<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="coinfaces">

        <title>未整理的</title>

    <link rel="stylesheet" href="../../theme/css/style.css">
    <link rel="stylesheet" href="../../theme/css/pygments.css">



</head>

<body>
    <div class="content">
<h1>未整理的</h1>
    <h3>在看的</h3>
<ul>
<li><a href="https://www.whitehatsec.com/blog/list-of-http-response-headers/">HTTP 返回消息头中的常见字段</a></li>
<li><a href="https://pyformat.info/">Python 字符串格式化参考链接</a></li>
<li><a href="https://stackoverflow.com/a/26672182/9634290">Django 创建模型的数据的2种方法</a></li>
<li><a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">wikipedia IANA 时区数据列表</a></li>
<li><a href="https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms">nginx location 参数规则详解</a></li>
<li><a href="http://www.huaxiaozhuan.com/">华校专 - AI 算法工程师手册</a></li>
<li><a href="https://www.jianshu.com/p/1323640d0157">跨系统鼠标、键盘共享</a></li>
</ul>
<h3>让git正常显示路径中的非ASCII字符</h3>
<p>git默认将路径中的非ASCII字符显示为\nnn的形式，有两种方式来处理这种输出，一种是通过bash的printf语句将其输出成普通人看得懂的文字：</p>
<div class="highlight"><pre><span></span><code><span class="nb">printf</span> <span class="s1">&#39;\344\275\240&#39;</span>
</code></pre></div>


<p>从git 1.7.10开始，可以通过修改git默认配置使其不转义路径中的非ASCII字符（<a href="https://stackoverflow.com/a/22828826/9634290">来源</a>）</p>
<div class="highlight"><pre><span></span><code>git config --global core.quotepath off
</code></pre></div>


<h3>React Router定义登录验证后才可以访问的页面</h3>
<p>网上搜到的定义PrivateRoute的方法大同小异，例如以下代码来自<a href="https://github.com/ReactTraining/react-router/blob/7a9170d759af1a02a473d631f411459aeaa562c2/packages/react-router-dom/examples/Auth.js">React Training提供的示例</a>：</p>
<div class="highlight"><pre><span></span><code><span class="err">function PrivateRoute({ children, ...rest }) {</span>
<span class="err">  return (</span>
<span class="err">    &lt;Route</span>
<span class="err">      {...rest}</span>
<span class="err">      render={({ location }) =&gt;</span>
<span class="err">        fakeAuth.isAuthenticated ? (</span>
<span class="err">          children</span>
<span class="err">        ) : (</span>
<span class="err">          &lt;Redirect</span>
<span class="err">            to={{</span>
<span class="err">              pathname: &quot;/login&quot;,</span>
<span class="err">              state: { from: location }</span>
<span class="err">            }}</span>
<span class="err">          /&gt;</span>
<span class="err">        )</span>
<span class="err">      }</span>
<span class="err">    /&gt;</span>
<span class="err">  );</span>
<span class="err">}</span>
</code></pre></div>


<p>这种基于重定义的<code>Route</code>组件的<code>render</code>属性的做法并不靠谱，如果不小心用了一下<code>component</code>属性，就会发现检查用户是否登录验证的代码失效了：</p>
<div class="highlight"><pre><span></span><code>// 从
<span class="nt">&lt;PrivateRoute</span> <span class="na">path=</span><span class="s">&quot;/protected&quot;</span><span class="nt">&gt;</span>
  <span class="nt">&lt;ProtectedPage</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/PrivateRoute&gt;</span>

// 改为
<span class="nt">&lt;PrivateRoute</span> <span class="na">path=</span><span class="s">&quot;/protected&quot;</span> <span class="na">component=</span><span class="s">{ProtectedPage}</span><span class="nt">&gt;</span>
  <span class="nt">&lt;ProtectedPage</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/PrivateRoute&gt;</span>
// 或者
<span class="nt">&lt;PrivateRoute</span> <span class="na">path=</span><span class="s">&quot;/protected&quot;</span> <span class="na">component=</span><span class="s">{ProtectedPage}</span> <span class="nt">/&gt;</span>
</code></pre></div>


<p>这是因为<code>render</code>属性的优先级是最低的，见<a href="https://github.com/ReactTraining/react-router/blob/7a9170d759af1a02a473d631f411459aeaa562c2/packages/react-router/modules/Route.js">React Router源码</a>：</p>
<div class="highlight"><pre><span></span><code>let { children, component, render } = this.props;

// Preact uses an empty array as children by
// default, so use null if that&#39;s the case.
if (Array.isArray(children) <span class="err">&amp;&amp;</span> children.length === 0) {
  children = null;
}

// 优先级：children &gt; component &gt; render
return (
  <span class="nt">&lt;RouterContext.Provider</span> <span class="na">value=</span><span class="s">{props}</span><span class="nt">&gt;</span>
    {props.match
      ? children
        ? typeof children === &quot;function&quot;
          ? __DEV__
            ? evalChildrenDev(children, props, this.props.path)
            : children(props)
          : children
        : component
        ? React.createElement(component, props)
        : render
        ? render(props)
        : null
      : typeof children === &quot;function&quot;
      ? __DEV__
        ? evalChildrenDev(children, props, this.props.path)
        : children(props)
      : null}
  <span class="nt">&lt;/RouterContext.Provider&gt;</span>
);
</code></pre></div>


<p>如果非要在自定义的<code>render</code>函数里完成所有工作，需要全面考虑<code>PrivateRoute</code>里定义了<code>children</code>、<code>component</code>和<code>render</code>的情况（在传给<code>Route</code>组件的自定义<code>render</code>函数里把以上源码中复杂的逻辑再重复一遍），才能实现和<code>Route</code>兼容的API。不如把判断用户是否登录的逻辑放到<code>Route</code>组件外面，让<code>Route</code>组件去处理不需要关心的内容：</p>
<div class="highlight"><pre><span></span><code><span class="err">function PrivateRoute(props) {</span>
<span class="err">  if (fakeAuth.isAuthenticated) {</span>
<span class="err">    return (&lt;Route {...props} /&gt;)</span>
<span class="err">  } else {</span>
<span class="err">    const { children, component, render, ...rest } = props;</span>
<span class="err">    return (</span>
<span class="err">      &lt;Route</span>
<span class="err">        {...rest}</span>
<span class="err">        render={({ location }) =&gt;</span>
<span class="err">          &lt;Redirect</span>
<span class="err">            to={{</span>
<span class="err">              pathname: &quot;/login&quot;,</span>
<span class="err">              state: { from: location }</span>
<span class="err">            }}</span>
<span class="err">          /&gt;</span>
<span class="err">        }</span>
<span class="err">      /&gt;</span>
<span class="err">    );</span>
<span class="err">  }</span>
<span class="err">}</span>
</code></pre></div>


<h3>URL编码参考</h3>
<p>w3school整理了一个<a href="https://www.w3schools.com/tags/ref_urlencode.asp">参考页面</a>，可以看到，同一文本，不同的字符集对应的URL编码可能不一样，因此它是在 GBK、UTF-8 编码后的字节串的基础上进行的再编码：</p>
<div class="highlight"><pre><span></span><code>     <span class="err">字符集编码</span>          <span class="n">URL编码</span>
<span class="err">文本</span> <span class="c1">----------&gt; 字节串 ---------&gt; URL编码后的ASCII字符串</span>

                      <span class="n">URL解码</span>          <span class="err">字符集解码</span>
<span class="n">URL编码后的ASCII字符串</span> <span class="c1">---------&gt; 字节串 ----------&gt; 文本</span>
</code></pre></div>


<h3>TypScript 模块定义和模块扩展</h3>
<p>在 Vue 项目中使用 TypeScript 时，会用到下面的定义：</p>
<div class="highlight"><pre><span></span><code><span class="kr">declare</span> <span class="nx">module</span> <span class="s1">&#39;*.vue&#39;</span> <span class="p">{</span>
  <span class="kr">import</span> <span class="nx">Vue</span> <span class="nx">from</span> <span class="s1">&#39;vue&#39;</span>
  <span class="kr">export</span> <span class="k">default</span> <span class="nx">Vue</span>
<span class="p">}</span>
</code></pre></div>


<p>这里如果把 import 语句放到 declare 语句块外部</p>
<div class="highlight"><pre><span></span><code><span class="kr">import</span> <span class="nx">Vue</span> <span class="nx">from</span> <span class="s1">&#39;vue&#39;</span>
<span class="kr">declare</span> <span class="nx">module</span> <span class="s1">&#39;*.vue&#39;</span> <span class="p">{</span>
  <span class="kr">export</span> <span class="k">default</span> <span class="nx">Vue</span>
<span class="p">}</span>
</code></pre></div>


<p>便不起作用，因为这两种语义是不同的，最外层的 import 语句表示<a href="https://github.com/microsoft/TypeScript/issues/28097#issuecomment-432722670">整个源文件是一个模块</a>，因此这里模块定义（module declaration）就变成了模块扩展（module argumentation），这种用法常用于往现有模块中添加内容，例如定义浏览器中的全局 Vue 对象 <code>window.Vue</code>：</p>
<div class="highlight"><pre><span></span><code><span class="kr">import</span> <span class="p">{</span> <span class="nx">VueConstructor</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;vue&#39;</span><span class="p">;</span>

<span class="kr">declare</span> <span class="nx">global</span> <span class="p">{</span>
  <span class="kr">interface</span> <span class="nx">Window</span> <span class="p">{</span>
    <span class="nx">Vue</span>: <span class="kt">VueConstructor</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>


<h3>GRIB 无效值</h3>
<p>就<a href="https://confluence.ecmwf.int/display/UDOC/Why+am+I+not+able+to+set+the+missing+value+in+the+GRIB+message+-+ecCodes+GRIB+FAQ">目前掌握的信息</a>，GRIB格式的文件没有用无效值（missing/undef/fill value）来编码。</p>
<blockquote>
<p>The GRIB format does keep track of missing values but through the use of a bitmap  It does not allow the specification of a missing value.</p>
<p>A default value of 9999 is set for the missing value in the library (not the GRIB message!). That means that when retrieving the values from a message without having set the missing value key, all missing values in the data will be replaced with the default value of 9999.</p>
</blockquote>
<p>至于解码后这些无效的数据被设置成什么值，不同工具有不同的处理方法：</p>
<p><a href="https://ftp.cpc.ncep.noaa.gov/wd51we/wgrib/wgrib.c">wgrib</a>使用<code>9.999e20</code>。</p>
<div class="highlight"><pre><span></span><code><span class="cm">/* undefined value -- if bitmap */</span>
<span class="cp">#define UNDEFINED       9.999e20</span>

<span class="c1">// lengthy code</span>

<span class="cm">/* 1996             wesley ebisuzaki</span>
<span class="cm"> *</span>
<span class="cm"> * Unpack BDS section</span>
<span class="cm"> *</span>
<span class="cm"> * input: *bits, pointer to packed integer data</span>
<span class="cm"> *        *bitmap, pointer to bitmap (undefined data), NULL if none</span>
<span class="cm"> *        n_bits, number of bits per packed integer</span>
<span class="cm"> *        n, number of data points (includes undefined data)</span>
<span class="cm"> *        ref, scale: flt[] = ref + scale*packed_int</span>
<span class="cm"> * output: *flt, pointer to output array</span>
<span class="cm"> *        undefined values filled with UNDEFINED</span>
<span class="cm"> *</span>
<span class="cm"> * note: code assumes an integer &gt; 32 bits</span>
<span class="cm"> *</span>
<span class="cm"> * 7/98 v1.2.1 fix bug for bitmaps and nbit &gt;= 25 found by Larry Brasfield</span>
<span class="cm"> * 2/01 v1.2.2 changed jj from long int to double</span>
<span class="cm"> * 3/02 v1.2.3 added unpacking extensions for spectral data</span>
<span class="cm"> *             Luis Kornblueh, MPIfM</span>
<span class="cm"> * 7/06 v.1.2.4 fixed some bug complex packed data was not set to undefined</span>
<span class="cm"> */</span>
</code></pre></div>


<p><a href="ftp://ftp.cpc.ncep.noaa.gov/wd51we/wgrib.scripts/grib2ctl.pl">grib2ctl</a>遵从这个约定：</p>
<div class="highlight"><pre><span></span><code><span class="k">print</span> <span class="s">&quot;undef 9.999E+20\ntitle $file\n*  produced by grib2ctl v$version\n&quot;</span><span class="p">;</span>
</code></pre></div>


<p><a href="https://www.ncl.ucar.edu/Support/talk_archives/2009/0844.html">NCL</a>目前使用<code>1e20</code></p>
<blockquote>
<p>The value assigned for the
_FillValues used to be the default fill value for float data in NCL,
-999.0, but because this value occasionally collided with real data
values, as of 5.1.0 it has been changed to 1e20 for GRIB data.</p>
</blockquote>
<p>这封邮件列表也提到某些GRIB扩展允许指定无效值，本处不作深究。</p>
<h3>MySQL 备份与恢复</h3>
<p>备份：</p>
<div class="highlight"><pre><span></span><code>mysqldump --add-drop-table -h <span class="si">${</span><span class="nv">host</span><span class="si">}</span> -u <span class="si">${</span><span class="nv">user</span><span class="si">}</span> -p <span class="si">${</span><span class="nv">database</span><span class="si">}</span> &gt;<span class="si">${</span><span class="nv">dumped_sql_file</span><span class="si">}</span>
</code></pre></div>


<p>恢复：</p>
<div class="highlight"><pre><span></span><code>mysql -h <span class="si">${</span><span class="nv">host</span><span class="si">}</span> -u <span class="si">${</span><span class="nv">user</span><span class="si">}</span> -p <span class="si">${</span><span class="nv">database</span><span class="si">}</span> &lt;<span class="si">${</span><span class="nv">dumped_sql_file</span><span class="si">}</span>
</code></pre></div>


<h3>MySQL 字符集设置</h3>
<p>参考<a href="https://stackoverflow.com/a/766996/9634290">这篇问答</a>：</p>
<ul>
<li>应该使用 utf8mb4 字符集，而不是 utf8 字符集，utf8
    字符集只支持最长3个字节的字符编码，现实生活中 utf8
    字符编码可达到4个字节。</li>
<li>COLLATE 不要使用 utf8mb4_general_ci，因为它不能正确处理 unicode
    字符的排序，至少应该使用 utf8mb4_unicode_ci，甚至应该使用
    utf8mb4_unicode_520_ci。</li>
</ul>
<h3>MySQL链接到非标准端口的服务器</h3>
<p>使用 <code>mysql</code> 命令连接不在3306端口监听的服务器时，只输入 <code>-P $port</code> 参数是不够的，一定要配合 <code>-h $ip</code> 参数使用：</p>
<div class="highlight"><pre><span></span><code>mysql -h <span class="nv">$ip</span> -P <span class="nv">$port</span> -u <span class="nv">$user</span> -p
</code></pre></div>


<p>因为不指定host参数或者host参数是 <code>localhost</code> 的时候，客户端会尝试连接本地的unix socket，因此，以下命令</p>
<div class="highlight"><pre><span></span><code>mysql -h <span class="nv">$ip</span> -P <span class="nv">$port</span> -u <span class="nv">$user</span> -p
</code></pre></div>


<p>不会按照期待的方式运行，如果本地有MySQL服务在监听unix socket，会连接到该服务，否则会报错</p>
<div class="highlight"><pre><span></span><code><span class="err">ERROR 2002 (HY000): Can&#39;t connect to local MySQL server through socket &#39;/var/run/mysqld/mysqld.sock&#39; (2)</span>
</code></pre></div>


<h3>CentOS 7 安装设置 phpMyAdmin</h3>
<p>首先需要安装必要的软件：</p>
<div class="highlight"><pre><span></span><code>yum install httpd php phpMyAdmin
</code></pre></div>


<p>修改 /etc/phpMyAdmin/config.inc.php 确保 phpMyAdmin 可以正确连接上 MySQL 数据库，phpMyAdmin 默认连接 127.0.0.1 的 3306 端口，如果 mysqld 没有在此端口监听，则需要修改 <code>$cfg['Servers'][$i]['host']</code> 和 <code>$cfg['Servers'][$i]['port']</code> 配置。如果使用 UNIX socket，则需要设置 <code>$cfg['Servers'][$i]['socket']</code> 选项，并且 <code>$cfg['Servers'][$i]['connect_type']</code> 配置需要设为 <code>'socket'</code>。</p>
<p>启动 httpd 服务：</p>
<div class="highlight"><pre><span></span><code>systemctl start httpd
</code></pre></div>


<p>在浏览器中打开 http://127.0.0.1/phpMyAdmin/ 就可以使用了。</p>
<h3>MySQL 命令</h3>
<p>列出服务器上所有的数据库（以下两条命令等价）：</p>
<div class="highlight"><pre><span></span><code><span class="k">SHOW</span> <span class="n">DATABASES</span><span class="p">;</span>
<span class="k">SHOW</span> <span class="n">SCHEMAS</span><span class="p">;</span>
</code></pre></div>


<p>列出数据库中所有数据表（或者参考“显示数据库中各数据表的记录数”）：</p>
<div class="highlight"><pre><span></span><code><span class="n">USE</span> <span class="o">&lt;</span><span class="n">your_db</span><span class="o">&gt;</span><span class="p">;</span>
<span class="k">SHOW</span> <span class="n">TABLES</span><span class="p">;</span>
</code></pre></div>


<p>显示数据库中各数据表的记录数：</p>
<div class="highlight"><pre><span></span><code><span class="k">SELECT</span> <span class="k">TABLE_NAME</span><span class="p">,</span> <span class="n">TABLE_ROWS</span>
     <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">TABLES</span>
     <span class="k">WHERE</span> <span class="n">TABLE_SCHEMA</span> <span class="o">=</span> <span class="s1">&#39;&lt;your_db&gt;&#39;</span><span class="p">;</span>
</code></pre></div>


<p>删除数据库：</p>
<div class="highlight"><pre><span></span><code><span class="k">DROP</span> <span class="k">DATABASE</span> <span class="p">[</span><span class="k">IF</span> <span class="k">EXISTS</span><span class="p">]</span> <span class="o">&lt;</span><span class="n">database_name</span><span class="o">&gt;</span><span class="p">;</span>
</code></pre></div>


<p>清空数据表</p>
<div class="highlight"><pre><span></span><code><span class="n">USE</span> <span class="o">&lt;</span><span class="n">your_db</span><span class="o">&gt;</span><span class="p">;</span>
<span class="k">DELETE</span> <span class="k">FROM</span> <span class="o">&lt;</span><span class="k">table</span><span class="o">&gt;</span><span class="p">;</span>
</code></pre></div>


<p>查看数据表的设计</p>
<div class="highlight"><pre><span></span><code><span class="k">DESCRIBE</span> <span class="p">[</span><span class="n">db_name</span><span class="p">.]</span><span class="k">table_name</span><span class="p">;</span>
</code></pre></div>


<p>查看创建数据表的 SQL 语句</p>
<div class="highlight"><pre><span></span><code><span class="k">SHOW</span> <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="p">[</span><span class="n">db_name</span><span class="p">.]</span><span class="k">table_name</span><span class="p">;</span>
</code></pre></div>


<p>删除数据表的字段</p>
<div class="highlight"><pre><span></span><code><span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">tbl_Country</span> <span class="k">DROP</span> <span class="k">COLUMN</span> <span class="n">IsDeleted</span><span class="p">;</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">tbl_Country</span>
  <span class="k">DROP</span> <span class="k">COLUMN</span> <span class="n">IsDeleted</span><span class="p">,</span>
  <span class="k">DROP</span> <span class="k">COLUMN</span> <span class="n">CountryName</span><span class="p">;</span>
</code></pre></div>


<p>添加数据</p>
<div class="highlight"><pre><span></span><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="o">`</span><span class="n">django_migrations</span><span class="o">`</span> <span class="p">(</span><span class="o">`</span><span class="n">app</span><span class="o">`</span><span class="p">,</span> <span class="o">`</span><span class="n">name</span><span class="o">`</span><span class="p">,</span> <span class="o">`</span><span class="n">applied</span><span class="o">`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">&#39;api&#39;</span><span class="p">,</span> <span class="s1">&#39;0003_auto_20190524_0716&#39;</span><span class="p">,</span> <span class="s1">&#39;2019-05-24 07:18:00&#39;</span><span class="p">);</span>
</code></pre></div>


<p>修改数据</p>
<div class="highlight"><pre><span></span><code><span class="k">UPDATE</span> <span class="k">table_name</span>
<span class="k">SET</span>
    <span class="n">field1</span> <span class="o">=</span> <span class="n">value1</span><span class="p">,</span>
    <span class="n">field2</span> <span class="o">=</span> <span class="n">value2</span>
<span class="k">WHERE</span>
    <span class="n">condition_expression</span><span class="p">;</span>
</code></pre></div>


<p>把字段设置为 <code>NULL</code></p>
<div class="highlight"><pre><span></span><code><span class="k">UPDATE</span> <span class="n">your_table</span>
<span class="k">SET</span>
    <span class="n">your_column</span> <span class="o">=</span> <span class="k">NULL</span>
<span class="k">WHERE</span>
    <span class="n">condition_expression</span><span class="p">;</span>
</code></pre></div>


<p>筛选字段为 <code>NULL</code> 的记录</p>
<div class="highlight"><pre><span></span><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">your_table</span> <span class="k">WHERE</span> <span class="n">your_column</span> <span class="k">IS</span> <span class="k">NULL</span><span class="p">;</span>
</code></pre></div>


<h3>Webpack 在编译时自动注入变量</h3>
<div class="highlight"><pre><span></span><code><span class="c1">// webpack.config.js</span>
<span class="kr">const</span> <span class="nx">webpack</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;webpack&#39;</span><span class="p">)</span>

<span class="p">{</span>
  <span class="c1">// other settings</span>
  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span>
    <span class="k">new</span> <span class="nx">webpack</span><span class="p">.</span><span class="nx">DefinePlugin</span><span class="p">({</span>
      <span class="nx">__WEBPACK_INJECT_VERSION__</span><span class="o">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;package.json&#39;</span><span class="p">).</span><span class="nx">version</span><span class="p">)</span>
    <span class="p">})</span>
  <span class="p">]</span>
<span class="p">}</span>
</code></pre></div>


<p>也可以直接在源代码中写 <code>require('./package.json').version</code>，但这样会导入 package.json 的全部内容。</p>
<h3>JavaScript 数组添加/删除元素</h3>
<ul>
<li>往数组头部添加（<code>unshift</code>）和删除（<code>shift</code>）元素</li>
<li>往数组尾部添加（<code>push</code>）和删除（<code>pop</code>）元素</li>
</ul>
<p>详见<a href="https://stackoverflow.com/a/8073687/9634290">这篇讨论</a></p>
<h3>Django 操作</h3>
<p>启用令环验证，确保以下两点：</p>
<div class="highlight"><pre><span></span><code><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">(</span>
    <span class="c1"># other apps</span>
    <span class="s1">&#39;rest_framework.authtoken&#39;</span>
<span class="p">)</span>

<span class="n">REST_FRAMEWORK</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s1">&#39;DEFAULT_AUTHENTICATION_CLASSES&#39;</span><span class="p">:</span> <span class="p">(</span>
        <span class="s1">&#39;rest_framework.authentication.TokenAuthentication&#39;</span><span class="p">,</span>
        <span class="c1"># other authentication classes</span>
    <span class="p">),</span>
    <span class="c1"># other configurations</span>
<span class="p">}</span>
</code></pre></div>


<p>自定义 <code>ViewSet</code> 的 <code>queryset</code>，把 <code>queryset</code> 类属性删除，用 <code>get_queryset</code> 方法代替：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">get_queryset</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">accounts</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
</code></pre></div>


<p>与此同时，因为 <code>queryset</code> 属性的缺失，必须在注册路由表的时候提供 <code>basename</code> 参数，因为 DRF 无法根据 <code>queryset</code> 属性自动判断 <code>basename</code> 的值，<code>basename</code> 会被用于<a href="https://www.django-rest-framework.org/api-guide/routers/#simplerouter">生成路由名称</a>。</p>
<p>获得空的 <code>queryset</code>：<code>MyModel.objects.none()</code>。</p>
<h3>让 ESLint 检查 TypeScript 源文件的代码风格</h3>
<p>下面说的 <em>.ts 文件中不存在 no-unused-vars 假警报的问题，这是因为 ESLint 根本没有检查 </em>.ts 文件。开启对 *.ts 文件的代码风格检查：</p>
<ul>
<li>
<p>命令行中把 <code>.ts</code> 后缀添加到 <code>--ext</code> 选项的后缀列表中（未测试）</p>
</li>
<li>
<p>eslint-loader 在规则的 test 正则表达式中匹配 *.ts 文件：</p>
</li>
</ul>
<p><code>javascript
  test: /\.(js|ts|vue)$/</code></p>
<ul>
<li>Visual Studio Code 把 <code>"typescript"</code> 添加到 <code>"eslint.validate"</code> 配置中：</li>
</ul>
<p><code>json
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ]</code></p>
<h3>解决 Vue 单文件组件中 TypeScript 类型 no-unused-vars 假警报的问题</h3>
<p>在 *.ts 文件中没有这个问题，原因不清楚，有问题的.eslintrc.js内容如下：</p>
<div class="highlight"><pre><span></span><code><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
  <span class="nx">root</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="nx">parserOptions</span><span class="o">:</span> <span class="p">{</span>
    <span class="nx">parser</span><span class="o">:</span> <span class="s1">&#39;@typescript-eslint/parser&#39;</span><span class="p">,</span>
    <span class="nx">sourceType</span><span class="o">:</span> <span class="s1">&#39;module&#39;</span>
  <span class="p">},</span>
  <span class="nx">env</span><span class="o">:</span> <span class="p">{</span>
    <span class="nx">browser</span><span class="o">:</span> <span class="kc">true</span>
  <span class="p">},</span>
  <span class="kr">extends</span><span class="o">:</span> <span class="p">[</span>
    <span class="c1">// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention</span>
    <span class="c1">// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.</span>
    <span class="s1">&#39;plugin:vue/essential&#39;</span><span class="p">,</span>
    <span class="c1">// https://github.com/standard/standard/blob/master/docs/RULES-en.md</span>
    <span class="s1">&#39;standard&#39;</span>
  <span class="p">],</span>
  <span class="c1">// required to lint *.vue files</span>
  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span>
    <span class="s1">&#39;vue&#39;</span>
  <span class="p">],</span>
  <span class="nx">globals</span><span class="o">:</span> <span class="p">{</span>
    <span class="s1">&#39;ga&#39;</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// Google Analytics</span>
    <span class="s1">&#39;cordova&#39;</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="s1">&#39;__statics&#39;</span><span class="o">:</span> <span class="kc">true</span>
  <span class="p">},</span>
  <span class="c1">// add your custom rules here</span>
  <span class="s1">&#39;rules&#39;</span><span class="o">:</span> <span class="p">{</span>
    <span class="c1">// allow async-await</span>
    <span class="s1">&#39;generator-star-spacing&#39;</span><span class="o">:</span> <span class="s1">&#39;off&#39;</span><span class="p">,</span>

    <span class="c1">// allow paren-less arrow functions</span>
    <span class="s1">&#39;arrow-parens&#39;</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="s1">&#39;one-var&#39;</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>

    <span class="s1">&#39;import/first&#39;</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="s1">&#39;import/named&#39;</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
    <span class="s1">&#39;import/namespace&#39;</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
    <span class="s1">&#39;import/default&#39;</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
    <span class="s1">&#39;import/export&#39;</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
    <span class="s1">&#39;import/extensions&#39;</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="s1">&#39;import/no-unresolved&#39;</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="s1">&#39;import/no-extraneous-dependencies&#39;</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>

    <span class="c1">// allow debugger during development</span>
    <span class="s1">&#39;no-debugger&#39;</span><span class="o">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NODE_ENV</span> <span class="o">===</span> <span class="s1">&#39;production&#39;</span> <span class="o">?</span> <span class="mi">2</span> <span class="o">:</span> <span class="mi">0</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>


<p>解决的办法是用用 @typescript-eslint/no-unused-vars 规则替换 no-unused-vars 规则，在 <code>plugins</code> 部分添加：</p>
<div class="highlight"><pre><span></span><code><span class="s1">&#39;@typescript-eslint&#39;</span>
</code></pre></div>


<p>在 <code>rules</code> 部分明确以下规则：</p>
<div class="highlight"><pre><span></span><code><span class="s1">&#39;no-unused-vars&#39;</span><span class="o">:</span> <span class="s1">&#39;off&#39;</span><span class="p">,</span>
<span class="s1">&#39;@typescript-eslint/no-unused-vars&#39;</span><span class="o">:</span> <span class="s1">&#39;error&#39;</span>
</code></pre></div>


<p>受 <a href="https://nuxtjs.org/guide/typescript/">Nuxt.js TypeScript 文档</a> 的启发：</p>
<div class="highlight"><pre><span></span><code><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;@typescript-eslint&#39;</span><span class="p">],</span>
  <span class="nx">parserOptions</span><span class="o">:</span> <span class="p">{</span>
    <span class="nx">parser</span><span class="o">:</span> <span class="s1">&#39;@typescript-eslint/parser&#39;</span>
  <span class="p">},</span>
  <span class="kr">extends</span><span class="o">:</span> <span class="p">[</span>
    <span class="s1">&#39;@nuxtjs&#39;</span>
  <span class="p">],</span>
  <span class="nx">rules</span><span class="o">:</span> <span class="p">{</span>
    <span class="s1">&#39;@typescript-eslint/no-unused-vars&#39;</span><span class="o">:</span> <span class="s1">&#39;error&#39;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>


<p><a href="https://github.com/typescript-eslint/typescript-eslint/issues/438#issuecomment-484570103"><code>@typescript-eslint/no-unused-vars</code> 是 eslint <code>no-unused-vars</code> 规则的的复制扩展</a>，其控制选项是一样的，只是规则的名称要从 <code>no-unused-vars</code> 改为 <code>@typescript-eslint/no-unused-vars</code>，如果两个规则同时打开，设置特例的时候要把设置特例的规则写两遍：</p>
<div class="highlight"><pre><span></span><code><span class="cm">/* eslint @typescript-eslint/no-unused-vars: [&quot;error&quot;, { &quot;varsIgnorePattern&quot;: &quot;^someVar$&quot; }] */</span>
<span class="cm">/* eslint no-unused-vars: [&quot;error&quot;, { &quot;varsIgnorePattern&quot;: &quot;^someVar$&quot; }] */</span>
</code></pre></div>


<h3>在 TypeScript 中传递构造函数</h3>
<p>构造函数和普通函数的类型不一样的地方在于其签名前面有一个 <code>new</code> 关键字：</p>
<div class="highlight"><pre><span></span><code><span class="kr">class</span> <span class="nx">MyClass</span> <span class="p">{</span>
  <span class="kr">constructor</span> <span class="p">(</span><span class="kr">public</span> <span class="nx">x</span>: <span class="kt">number</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">createInstance</span> <span class="p">(</span><span class="nx">Constructor</span>: <span class="kt">new</span> <span class="p">(</span><span class="nx">x</span>: <span class="kt">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">MyClass</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="k">new</span> <span class="nx">Constructor</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>


<p>如果不知道构造函数的参数，用 <code>new (...args: any[]) =&gt; any</code> 表示任意构造函数。</p>
<p><a href="https://stackoverflow.com/a/12871488/9634290">讨论链接</a>。</p>
<h3>SQLite3 命令行不支持 readline 的处理</h3>
<ul>
<li>输入命令 <code>which sqlite3</code>，检查 Linux 发行版提供的 sqlite3 命令是否<a href="https://superuser.com/a/353991">被第三方提供的 sqlite3 命令屏蔽</a>，如有，可以通过添加命令别名或者修改 PATH 环境变量切换到发行版提供的 sqlite3。</li>
<li>如果 Linux 发行版没有提供支持 readline 的 sqlite3 命令或者无法安装支持 readline 的 sqlite3 版本，使用 rlwrap 启动 sqlite3：</li>
</ul>
<p><code>sh
  rlwrap sqlite3 "$args"</code></p>
<h3>确保Vue重新渲染组件</h3>
<p>给Vue组件添加 <code>key</code> 绑定，确保数据发生变化时 <code>key</code> 的值也发生变化。</p>
<h3>git SSH代码库路径的两种格式</h3>
<p><a href="https://stackoverflow.com/a/5738592/9634290">基于SSH的代码库路径有两种格式</a>，URL形式 <code>ssh://[user@]host.xz[:port]/path/to/repo.git/</code> 可以指定SSH服务监听端口，SCP形式 <code>[user@]host.xz:path/to/repo.git/</code> 没有地方指定端口，需要在 ~/.ssh/config 中配置SSH服务器的信息。然后在地址中主机域名的位置填入主机别名： <code>my_git_host:path/to/repo.git/</code>。</p>
<div class="highlight"><pre><span></span><code><span class="err">Host my_git_host</span>
<span class="err">HostName git.some.host.org</span>
<span class="err">Port 24589</span>
<span class="err">User not_a_root_user</span>
</code></pre></div>


<h3>Supervisord 技巧</h3>
<p>修改了配置文件，可以运行 <code>supervisorctl reread</code> 命令加载修改后的配置文件，但只有运行 <code>supervisorctl update</code> 才会应用这些修改。在添加了新程序的情况下，不想中断旧程序的运行，可以用这种方法运行新程序。</p>
<p>如果修改了旧程序的配置文件 <code>supervisorctl update</code> 会导致旧程序重启，如果在远程服务器上运行此程序，并且退出旧程序的时候连接断开（例如远程连接建立在旧程序维持的 SSH 隧道上），则其不会重启，如果没有其它渠道登录远程服务器，便会失去和远程服务器的连接。</p>
<p>Supervisor 配置的 <code>startretries</code> 参数默认为3，<a href="https://github.com/Supervisor/supervisor/issues/487#issuecomment-355129495">据说（这是后面推论的基础）</a> supervisor 每次多延迟1秒钟尝试启动程序，这意味着如果3-6秒（取决于第一次尝试重启是0秒还是1秒）内程序因故无法启动，便会进入 <code>FATAL</code> 状态，被 supervisor 放弃，增加这个数字有助于推迟程序被放弃的时间，如果把 <code>startretries</code> 设为300，则这个时间延长到750分钟，达12个半小时之久，并且最长的尝试启动程序的间隔不超过5分钟。</p>
<p><code>autorestart=unexpected</code> 和 <code>autorestart=true</code>，这两种设置的区别是，程序从 <code>RUNNING</code> 状态中退出时，如果 exit code 在 <code>exitcodes</code> 列表（从 4.0 版本开始，<code>exitcodes</code> 的默认值是 <code>0</code>，之前是 <code>0,2</code>）中，前者不会重新启动任务，后者会重新启动任务。如果程序没有进入 <code>RUNNING</code> 状态，默认要持续运行 <code>startsecs</code>（默认值是1）秒，<code>autorestart</code> 选项不起作用，任务总会重启。</p>
<h3>SSH 发布端口到公网服务器</h3>
<p>运行下面的命令可以把端口发布到公网服务器上：</p>
<div class="highlight"><pre><span></span><code>ssh -R <span class="m">8022</span>:localhost:22 <span class="nv">$remote_ip</span>
</code></pre></div>


<p>这个命令在目标服务器的22端口和公网服务器的8022端口（loopback 网络界面 127.0.0.1，如需绑定其它 IP 地址，参考 <code>man ssh</code> 帮助）之间建立了隧道，任何时候都可以登录到公网服务器，然后运行 <code>ssh -p 8022 localhost</code> 登录到目标服务器。</p>
<p>部署一条目标服务器到公网服务器的端口需要满足3个条件：</p>
<ul>
<li>关闭该 SSH 连接除了端口代理之外的其它资源</li>
<li>目标服务器到公网服务器的 SSH 免密登录</li>
<li>守护进程</li>
</ul>
<p>把命令改为 <code>ssh -nNT -R 8022:localhost:22 $remote_ip</code> 可以建立一条除了隧道之外没有其它功能的 SSH 连接。</p>
<p>这里用公钥实现 SSH 免密登录，在目标服务器上运行一下命令生成 SSH 密钥：</p>
<div class="highlight"><pre><span></span><code>sh-keygen -f ~/.ssh/tunnel -t rsa
</code></pre></div>


<p>在公网服务器上建立专供隧道连接的 tunnel 账户：</p>
<div class="highlight"><pre><span></span><code>useradd -m tunnel
</code></pre></div>


<p>在公网服务器上</p>
<ul>
<li>创建 ~tunnel/.ssh 目录，权限 0700</li>
<li>创建 ~tunnel/.ssh/authorized_keys 文件，权限 0644，内容为目标服务器 ~/.ssh/tunnel.pub 中的公钥</li>
</ul>
<p>这时 <code>ssh tunnel@remote_ip</code> 命令应该可以借助公钥免密登录到公网服务器的 tunnel 账户，在后台运行 <code>ssh</code> 命令之前务必在前台运行一遍该命令（或者其它任何登录到该公网服务器的命令），以记录公网服务器提供的指纹，否则后台运行的 <code>ssh</code> 命令会因为公网服务器指纹提示得不到确认而失败。</p>
<p>最后用 supervisord 监控提供隧道连接的 <code>ssh</code> 命令，安装 supervisord，并且添加如下配置文件 /etc/supervisord.d/tunnel.ini：</p>
<div class="highlight"><pre><span></span><code><span class="k">[program:tunnel]</span>
<span class="na">autorestart</span><span class="o">=</span><span class="s">true</span>
<span class="na">startretries</span><span class="o">=</span><span class="s">300</span>

<span class="na">command</span><span class="o">=</span><span class="s">ssh -nNT -i /root/.ssh/tunnel -R 8022:localhost:22 tunnel@$remote_ip</span>

<span class="na">stdout_logfile</span><span class="o">=</span><span class="s">/var/log/supervisor/tunnel.out</span>
<span class="na">stdout_logfile_maxbytes</span><span class="o">=</span><span class="s">10MB</span>
<span class="na">stdout_logfile_backups</span><span class="o">=</span><span class="s">10</span>

<span class="na">stderr_logfile</span><span class="o">=</span><span class="s">/var/log/supervisor/tunnel.err</span>
<span class="na">stderr_logfile_maxbytes</span><span class="o">=</span><span class="s">10MB</span>
<span class="na">stderr_logfile_backups</span><span class="o">=</span><span class="s">10</span>
</code></pre></div>


<p>把 $remote_ip 替换成公网服务器的 IP 地址或域名，启动 supervisord 服务：</p>
<div class="highlight"><pre><span></span><code>systemctl start supervisord
</code></pre></div>


<p>最好把该服务设为开机启动：</p>
<div class="highlight"><pre><span></span><code>systemctl <span class="nb">enable</span> supervisord
</code></pre></div>


<p>如果不想每次都先登录公网服务器，然后再运行 <code>ssh -p 8022 localhost</code> 登录到目标服务器，可以再建立一条本地服务器到公网服务器的 SSH 隧道，不过这次运行的是 <code>ssh -L 8022:localhost:8022 tunnel@$remote_ip</code>，表示访问本地的8022端口相当于访问公网服务器的8022端口，也就相当于访问目标服务器的22端口。与 <code>ssh -R 0.0.0.0:8022:localhost:22 $remote_ip</code> 的方法相比，这种方法不需要公网服务器的 root 权限（修改 SSH 配置的 <code>GatewayPorts</code> 参数），也没有端口暴露到公网，因此更加安全。</p>
<h3>TypeScript 类型</h3>
<p>使用 TypeScript 3.5.1，打开 <code>--strict</code> 选项。</p>
<p>在 TypeScript 中，类型只是一种标记，用于类型推断和类型匹配检查，不影响程序的运行。如果类型注解有误，类型检查可以通过，但运行时错误在所难免。</p>
<p>类型名称和 JavaScript 关键字的名称可能一样，并且相互之间存在对应关系，如 <code>undefined</code>，也有可能使用 JavaScript 中没有的名称，如 <code>any</code>。JavaScript 内置类和用户自定义类的名称可以用作类型名，并且可以匹配该类的实例，但也存在例外，如 <code>Array</code> 在 TypeScript 中是泛型，必须包含参数。</p>
<p><code>boolean</code>、<code>number</code>、<code>string</code>、<code>symbol</code> 分别匹配布尔、数值、字符串、符号这些原始类型的变量。与之对应的 JavaScript 类 <code>Boolean</code>、<code>Number</code>、<code>String</code>、<code>Symbol</code> 也可以作为类型使用。除了 <code>Symbol</code> 和 <code>symbol</code> 匹配的类型相同（因为 <code>Symbol</code> 类不能构造实例），这些 JavaScript 内置类代表的类型是对应原始类型的超集。如 <code>Boolean</code> 可以匹配 <code>true</code> 和 <code>false</code>，但是 <code>boolean</code> 不能匹配 <code>Boolean</code> 的实例。</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">a</span>: <span class="kt">boolean</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Boolean</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>  <span class="c1">// TS2322: Type &#39;Boolean&#39; is not assignable to type &#39;boolean&#39;.</span>
<span class="kd">let</span> <span class="nx">b</span>: <span class="kt">Boolean</span> <span class="o">=</span> <span class="kc">true</span>  <span class="c1">// OK</span>
</code></pre></div>


<p><code>null</code> 和 <code>undefined</code> 类型分别匹配 <code>null</code> 和 <code>undefined</code>。</p>
<p><code>object</code> 类型匹配所有对象，但不能匹配原始类型的值，如 <code>Object.create</code> 的第一个参数的类型就可以精确地表达为 <code>null</code>。<code>Object</code> 类型也可以匹配所有的值，包括原始类型的值。<code>Object</code> 类型的值可以访问 <code>Object.prototype</code> 的属性，但 <code>object</code> 类型的值不可以访问任何属性。</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">a</span>: <span class="kt">object</span> <span class="o">=</span> <span class="mi">4</span>  <span class="c1">// TS2322: Type &#39;4&#39; is not assignable to type &#39;object&#39;.</span>
<span class="kd">let</span> <span class="nx">b</span>: <span class="kt">object</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Number</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">toFixed</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span>  <span class="c1">// TS2339: Property &#39;toFixed&#39; does not exist on type &#39;object&#39;.</span>
<span class="kd">let</span> <span class="nx">c</span>: <span class="kt">Object</span> <span class="o">=</span> <span class="mi">4</span>
<span class="nx">c</span> <span class="o">=</span> <span class="nx">Symbol</span><span class="p">.</span><span class="k">for</span><span class="p">(</span><span class="s1">&#39;4&#39;</span><span class="p">)</span>
</code></pre></div>


<p><code>{}</code> 类型也可以匹配所有的值，目前发现的其和 <code>Object</code> 类型的唯一区别是，Visual Studio Code 1.35.1 提示 <code>Object</code> 类型的属性自动完成，但不提示 <code>{}</code> 类型的属性自动完成，没有看到 <code>tsc</code> 在什么地方区分这两种类型。</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">a</span>: <span class="kt">Object</span> <span class="o">=</span> <span class="p">[]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="s1">&#39;length&#39;</span><span class="p">))</span>
<span class="kd">let</span> <span class="nx">b</span><span class="o">:</span> <span class="p">{}</span> <span class="o">=</span> <span class="p">[]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">b</span><span class="p">.</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="s1">&#39;length&#39;</span><span class="p">))</span>
</code></pre></div>


<p>越一般的类型，其匹配的范围越广，但可以使用的属性也越少，例如 <code>Object</code> 类型的变量，即使把字符串赋值给它，也不能直接访问 <code>length</code> 属性，可以用类型断言解决这个问题。</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">a</span>: <span class="kt">Object</span> <span class="o">=</span> <span class="s1">&#39;hello&#39;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span>  <span class="c1">// TS2339: Property &#39;length&#39; does not exist on type &#39;Object&#39;.</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">((</span><span class="o">&lt;</span><span class="nx">any</span><span class="p">[]</span><span class="o">&gt;</span><span class="nx">a</span><span class="p">).</span><span class="nx">length</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">((</span><span class="nx">a</span> <span class="kr">as</span> <span class="nx">any</span><span class="p">[]).</span><span class="nx">length</span><span class="p">)</span>  <span class="c1">// 类型断言的另一种写法和上面的语句等价</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">((</span><span class="nx">a</span> <span class="kr">as</span> <span class="kt">number</span><span class="p">).</span><span class="nx">toFixed</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span>  <span class="c1">// 抛出异常，TypeError: a.toFixed is not a function</span>
</code></pre></div>


<p>再次强调 TypeScript 类型不影响代码的运行，类型断言只用于把程序员推断的类型告诉 TypeScript 编译器，不进行类型转换。</p>
<p>类型断言并非只是告诉编译器此处的值是什么类型，也可以告诉编译器此处的值不是什么类型，如表达式后的 <code>!</code> 告诉编译器此处的值不可能是 <code>undefined</code> 或 <code>null</code>：</p>
<div class="highlight"><pre><span></span><code><span class="kd">function</span> <span class="nx">getStringLength</span> <span class="p">(</span><span class="nx">s</span>: <span class="kt">string</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">)</span> <span class="o">:</span> <span class="kt">number</span> <span class="o">|</span> <span class="kc">undefined</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">arguments</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// return s.length  // TS2532: Object is possibly &#39;undefined&#39;.</span>
    <span class="k">return</span> <span class="nx">s</span><span class="o">!</span><span class="p">.</span><span class="nx">length</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">getAge</span> <span class="p">(</span><span class="nx">person</span><span class="o">:</span> <span class="p">{</span> <span class="nx">name</span>: <span class="kt">string</span> <span class="o">|</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">age</span>: <span class="kt">number</span> <span class="o">|</span> <span class="kc">null</span> <span class="p">})</span><span class="o">:</span> <span class="kt">number</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">person</span><span class="p">.</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// return person.age  // TS2322: Type &#39;number | null&#39; is not assignable to type &#39;number&#39;.</span>
    <span class="k">return</span> <span class="p">(</span><span class="nx">person</span><span class="p">.</span><span class="nx">age</span><span class="p">)</span><span class="o">!</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">return</span> <span class="mi">0</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>


<p><code>any</code> 用于关闭类型检查，主要用于和 JavaScript 代码交互。<code>any</code> 类型可以匹配任意值，也可以访问任意属性，很特殊，其魔法来自于 TypeScript 不对涉及 <code>any</code> 类型的运算进行类型检查。</p>
<p>数组：<code>T[]</code> 或者 <code>Array&lt;T&gt;</code>，如 <code>Array&lt;number&gt;</code> 可以匹配数值元素组成的数组，<code>Array&lt;any&gt;</code> 可以匹配任意数组。</p>
<p>元祖类型（tuple）用于长度固定，并且每个位置的数组元素的类型可以确定的数组，如 <code>[string, number]</code> 可以匹配 <code>['PI', 3.14159]</code>。</p>
<p>枚举类型（enum）用于表示确定的选项，如 <code>enum Color { Red=1, Green, Blue}</code></p>
<p><code>void</code> 表示函数没有返回值，可以把它理解为 <code>undefined</code> 类型的别名，但有一处差别，<code>void</code> 类型可以匹配 <code>undefined</code> 类型的变量，但是反过来不可以：</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">a</span>: <span class="kt">void</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">b</span>: <span class="kt">undefined</span> <span class="o">=</span> <span class="nx">a</span><span class="p">;</span>  <span class="c1">// TS2322: Type &#39;void&#39; is not assignable to type &#39;undefined&#39;.</span>
</code></pre></div>


<p>TypeScript <a href="https://www.typescriptlang.org/docs/handbook/basic-types.html#void">文档</a>上说 <code>void</code> 类型可以匹配 <code>undefined</code> 和 <code>null</code>，但实际情况并非如此，参考<a href="https://github.com/Microsoft/TypeScript/issues/8322#issuecomment-214928203">这个解释</a>。</p>
<p><code>never</code> 表示没有值，如函数不返回（死循环或者抛出异常）：</p>
<div class="highlight"><pre><span></span><code><span class="kd">function</span> <span class="nx">die</span><span class="p">(</span><span class="nx">message</span>: <span class="kt">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">never</span> <span class="p">{</span>
  <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">getStringLength</span> <span class="p">(</span><span class="nx">s</span>: <span class="kt">string</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">)</span> <span class="o">:</span> <span class="kt">number</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">arguments</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">s</span><span class="o">!</span><span class="p">.</span><span class="nx">length</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">die</span><span class="p">(</span><span class="s1">&#39;Must provide a string&#39;</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">messageLoop</span> <span class="p">()</span><span class="o">:</span> <span class="nx">never</span> <span class="p">{</span>
  <span class="k">while</span> <span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// processMessage()</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>


<p>或者空数组：</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span> <span class="nx">a</span>: <span class="kt">Array</span><span class="o">&lt;</span><span class="nx">never</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">freeze</span><span class="p">([])</span>
</code></pre></div>


<p>可以为 <code>this</code> 添加类型注解，放在函数的第一个参数之前：</p>
<div class="highlight"><pre><span></span><code><span class="kr">class</span> <span class="nx">Person</span> <span class="p">{</span>
  <span class="kr">constructor</span> <span class="p">(</span><span class="kr">public</span> <span class="nx">firstName</span>: <span class="kt">string</span><span class="p">,</span> <span class="kr">public</span> <span class="nx">lastName</span>: <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>

  <span class="p">}</span>
<span class="p">}</span>

<span class="kr">interface</span> <span class="nx">Person</span> <span class="p">{</span>
  <span class="nx">getFullName</span><span class="o">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="kt">string</span><span class="p">;</span>
<span class="p">}</span>

<span class="nx">Person</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">getFullName</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="k">this</span><span class="o">:</span> <span class="nx">Person</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="sb">`</span><span class="si">${</span><span class="k">this</span><span class="p">.</span><span class="nx">firstName</span><span class="si">}</span><span class="sb"> </span><span class="si">${</span><span class="k">this</span><span class="p">.</span><span class="nx">lastName</span><span class="si">}</span><span class="sb">`</span>
<span class="p">}</span>
</code></pre></div>


<h3>JavaScript 对象属性</h3>
<p>这里讨论的属性都是对象自身的属性，即 <code>Object.getOwnPropertyNames()</code> 或 <code>Object.getOwnPropertySymbols()</code> 可以列出的属性。</p>
<p>JavaScript 对象的属性不是一个简单的值，还具有其它性质，这些性质包括：（<code>configurable</code>、<code>enumerable</code>、<code>value</code> 和 <code>writable</code>）或者（<code>configurable</code>、<code>enumerable</code>、<code>get</code> 和 <code>set</code>）。这些属性也可以用一个 JavaScript 对象来描述，这个对象称为描述符 descriptor。</p>
<p>可以看到属性的性质分为两种，<code>value</code> 和 <code>writable</code> 用于描述数据属性（data property），其描述符可称为数据描述符（data descriptor），<code>get</code> 和 <code>set</code> 用于描述访问器属性（accessor property），其描述符称为访问器描述符（accessor descriptor），两类属性共享 <code>configurable</code> 和 <code>enumerable</code>。这两类属性只能二选一，不能混用。</p>
<p>这些性质的作用可以参考 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"><code>Object.defineProperty()</code> 的 MDN 文档</a>。下面列出对单项属性进行操作需要的条件（假设对象本身没有限制属性的添加、删除和设置操作）：</p>
<ul>
<li>删除属性（<code>configurable === true</code>）</li>
<li>设置 <code>configurable</code>、<code>enumerable</code>、<code>get</code> 或 <code>set</code>（<code>configurable === true</code>）</li>
<li>切换数据/访问器属性（<code>configurable === true</code>）</li>
<li>把 <code>writable</code> 从 <code>false</code> 设置为 <code>true</code>（<code>configurable === true</code>）</li>
<li>把 <code>writable</code> 从 <code>true</code> 设置为 <code>false</code>（没有限制）</li>
<li>设置 <code>value</code>（<code>writable === true</code>）</li>
</ul>
<p>原型链上的属性可能会限制通过赋值操作创建对象的同名属性，如：</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="p">{}</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="s1">&#39;x&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">value</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">writable</span><span class="o">:</span> <span class="kc">false</span> <span class="p">})</span>
<span class="kd">var</span> <span class="nx">b</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">x</span> <span class="o">=</span> <span class="mi">2</span>  <span class="c1">// 无效，或在 strict mode 下抛出 TypeError</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">x</span>  <span class="c1">// 1</span>

<span class="c1">// b.x 不再继承自 a</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">b</span><span class="p">,</span> <span class="s1">&#39;x&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">value</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">writable</span><span class="o">:</span> <span class="kc">true</span> <span class="p">})</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">x</span>  <span class="c1">// 2</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">x</span> <span class="o">=</span> <span class="mi">3</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">x</span>  <span class="c1">// 3</span>
</code></pre></div>


<p>简单地理解就是，如果属性 <code>configurable === true</code>，则对于该项属性的操作是完全自由的，否则对该项属性进行操作的权限只能降低（把 <code>writable</code> 从 <code>true</code> 设置为 <code>false</code>），不能升高。</p>
<p>可以用 <code>Object.preventExtensions()</code>、<code>Object.seal()</code> 和 <code>Object.freeze()</code> 限制操作对象属性，这些操作都是不可逆的，其效果如下：</p>
<ul>
<li><code>Object.preventExtensions()</code>（不可添加属性，不可更改原型）</li>
<li><code>Object.seal()</code>（不可添加或删除属性，不可更改原型，对象非继承属性的 <code>configurable</code> 被设置为 <code>false</code>）</li>
<li><code>Object.freeze()</code>（不可添加属性，不可更改原型，对象非继承属性的 <code>configurable</code> 和 <code>writable</code> 被设置为 <code>false</code>）</li>
</ul>
<p>上述对于对象属性的操作都限制为对象自身，不包括子对象（类型为对象的数据属性），也不包括原型：</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="p">{}</span>
<span class="kd">var</span> <span class="nx">b</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">x</span> <span class="o">=</span> <span class="p">{}</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">freeze</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">x</span> <span class="o">=</span> <span class="mi">1</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">x</span>  <span class="c1">// {}</span>
<span class="nx">a</span><span class="p">.</span><span class="nx">y</span> <span class="o">=</span> <span class="mi">2</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">y</span>  <span class="c1">// 2</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">x</span><span class="p">.</span><span class="nx">z</span> <span class="o">=</span> <span class="mi">3</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">x</span><span class="p">.</span><span class="nx">z</span>  <span class="c1">// 3</span>
</code></pre></div>


<h3>Vue 会监测 JavaScript 对象哪些属性的变化</h3>
<p>以下代码来自 Vue 2.6.10 的 src/core/observer/index.js：</p>
<p>Vue 只为数组和普通对象添加 <code>__ob__</code> 属性。</p>
<div class="highlight"><pre><span></span><code><span class="cm">/**</span>
<span class="cm"> * Attempt to create an observer instance for a value,</span>
<span class="cm"> * returns the new observer if successfully observed,</span>
<span class="cm"> * or the existing observer if the value already has one.</span>
<span class="cm"> */</span>
<span class="kr">export</span> <span class="kd">function</span> <span class="nx">observe</span> <span class="p">(</span><span class="nx">value</span><span class="o">:</span> <span class="nx">any</span><span class="p">,</span> <span class="nx">asRootData</span><span class="o">:</span> <span class="o">?</span><span class="kr">boolean</span><span class="p">)</span><span class="o">:</span> <span class="nx">Observer</span> <span class="o">|</span> <span class="k">void</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isObject</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">||</span> <span class="nx">value</span> <span class="k">instanceof</span> <span class="nx">VNode</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span>
  <span class="p">}</span>
  <span class="kd">let</span> <span class="nx">ob</span><span class="o">:</span> <span class="nx">Observer</span> <span class="o">|</span> <span class="k">void</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">hasOwn</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="s1">&#39;__ob__&#39;</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">value</span><span class="p">.</span><span class="nx">__ob__</span> <span class="k">instanceof</span> <span class="nx">Observer</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">ob</span> <span class="o">=</span> <span class="nx">value</span><span class="p">.</span><span class="nx">__ob__</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span>
    <span class="nx">shouldObserve</span> <span class="o">&amp;&amp;</span>
    <span class="o">!</span><span class="nx">isServerRendering</span><span class="p">()</span> <span class="o">&amp;&amp;</span>
    <span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">||</span> <span class="nx">isPlainObject</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span> <span class="o">&amp;&amp;</span>
    <span class="nb">Object</span><span class="p">.</span><span class="nx">isExtensible</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
    <span class="o">!</span><span class="nx">value</span><span class="p">.</span><span class="nx">_isVue</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="nx">ob</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Observer</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span>
  <span class="p">}</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">asRootData</span> <span class="o">&amp;&amp;</span> <span class="nx">ob</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">ob</span><span class="p">.</span><span class="nx">vmCount</span><span class="o">++</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nx">ob</span>
<span class="p">}</span>
</code></pre></div>


<p>Vue 只监测对象 <code>Object.keys()</code> 可以列出的（<strong>非</strong>继承自原型链、可列举（Enumerable）、非 <code>Symbol</code> ）属性的变化。</p>
<div class="highlight"><pre><span></span><code><span class="cm">/**</span>
<span class="cm"> * Observer class that is attached to each observed</span>
<span class="cm"> * object. Once attached, the observer converts the target</span>
<span class="cm"> * object&#39;s property keys into getter/setters that</span>
<span class="cm"> * collect dependencies and dispatch updates.</span>
<span class="cm"> */</span>
<span class="kr">export</span> <span class="kr">class</span> <span class="nx">Observer</span> <span class="p">{</span>
  <span class="nx">value</span><span class="o">:</span> <span class="nx">any</span><span class="p">;</span>
  <span class="nx">dep</span><span class="o">:</span> <span class="nx">Dep</span><span class="p">;</span>
  <span class="nx">vmCount</span><span class="o">:</span> <span class="nx">number</span><span class="p">;</span> <span class="c1">// number of vms that have this object as root $data</span>

  <span class="nx">constructor</span> <span class="p">(</span><span class="nx">value</span><span class="o">:</span> <span class="nx">any</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">dep</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Dep</span><span class="p">()</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">vmCount</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="nx">def</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="s1">&#39;__ob__&#39;</span><span class="p">,</span> <span class="k">this</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">hasProto</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">protoAugment</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">arrayMethods</span><span class="p">)</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">copyAugment</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">arrayMethods</span><span class="p">,</span> <span class="nx">arrayKeys</span><span class="p">)</span>
      <span class="p">}</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">observeArray</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">walk</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**</span>
<span class="cm">   * Walk through all properties and convert them into</span>
<span class="cm">   * getter/setters. This method should only be called when</span>
<span class="cm">   * value type is Object.</span>
<span class="cm">   */</span>
  <span class="nx">walk</span> <span class="p">(</span><span class="nx">obj</span><span class="o">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="p">{</span>
    <span class="kr">const</span> <span class="nx">keys</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">obj</span><span class="p">)</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">keys</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">defineReactive</span><span class="p">(</span><span class="nx">obj</span><span class="p">,</span> <span class="nx">keys</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/* The rest of class Observer */</span>
<span class="p">}</span>
</code></pre></div>


<h3>yarn 全局安装路径</h3>
<p><code>yarn global</code>命令和<code>npm install -g</code>命令的安装位置存在差异，从npm转到yarn，有可能会遇到找不到yarn安装的全局命令的问题。</p>
<p>使用<code>yarn global bin</code>可以输出yarn安装的全局命令的可执行文件的位置，使用<code>yarn global dir</code>可以找到这些全局命令的软件包根目录位置（内含node_modules目录和yarn.lock文件）。在Linux系统中，非root用户的可执行文件位置和软件包根目录的位置分别为<code>~/.yarn/bin</code>和<code>~/.config/yarn/global</code>。</p>
<p>可以通过设置<code>prefix</code>参数来指定yarn全局命令的可执行目录的位置，如使用<code>yarn config set prefix ~/node</code>命令会把全局命令可执行文件位置设置为<code>~/node/bin</code>。</p>
<h3>用 yarn 更新依赖</h3>
<p>单个依赖：<code>yarn add ${package}</code>
全部依赖：<code>yarn upgrade</code></p>
<h3>yarn 离线软件库</h3>
<p>yarn 还支持<a href="https://yarnpkg.com/lang/en/docs/offline-mirror/">离线软件库</a>功能，目的是解决项目构建的可重复性和可靠性，线上软件库不能保证这两点。</p>
<p>离线软件库很容易设置，运行下面的命令在项目目录中创建 .yarnrc：</p>
<div class="highlight"><pre><span></span><code>yarn config <span class="nb">set</span> yarn-offline-mirror ./npm-offline-repo
yarn config <span class="nb">set</span> yarn-offline-mirror-pruning <span class="nb">true</span>
mv ~/.yarnrc .
</code></pre></div>


<p>然后把项目所需的软件包下载到离线软件库里：</p>
<div class="highlight"><pre><span></span><code>yarn cache clean
rm -rf node_modules/ yarn.lock
yarn install
</code></pre></div>


<p>离线软件库有如下好处：</p>
<ul>
<li>和 node_modules 相比，离线软件库只包含源码的包，不包含编译后的目标文件，也没有软件包之间复杂的目录结构，软件包的添加、删除一目了然</li>
<li>和离线缓存相比，离线软件库的目录结构也更加简洁，与软件依赖管理工具无关</li>
<li>和镜像相比（虽然 yarn 把离线软件库称为 offline mirror，但它们之间不能等同），离线软件库只包含项目依赖的软件包，并可保证项目不再依赖的软件包及时删除，同步成本低，占用空间小</li>
</ul>
<p>简单的说，离线软件库赢在轻量、简单、工具无关。</p>
<h3>yarn 修改次级依赖的软件包版本号</h3>
<p>quasar-cli 把 Vue 的版本号写死为 2.5.17，而想要使用新的 slot 语法，必须将 Vue 升级到 2.6，yarn 通过 <a href="https://yarnpkg.com/lang/en/docs/selective-version-resolutions/">Selective dependency resolutions</a> 优雅地解决了这个问题。</p>
<p>首先把软件依赖管理工具从 npm 切换到 yarn，在项目目录下运行 <code>yarn import</code>，然后删除 package-lock.json 即可。yarn 默认使用 http://registry.yarnpkg.com/，可以通过 <code>--registry</code> 参数修改为其它 npm registry。</p>
<p>最后在 package.json 中添加 <code>"resolutions"</code> 记录：</p>
<div class="highlight"><pre><span></span><code><span class="s2">&quot;resolutions&quot;</span><span class="err">:</span> <span class="p">{</span>
  <span class="nt">&quot;quasar-cli/vue&quot;</span><span class="p">:</span> <span class="s2">&quot;^2.6.0&quot;</span><span class="p">,</span>
  <span class="nt">&quot;quasar-cli/vue-server-renderer&quot;</span><span class="p">:</span><span class="s2">&quot;^2.6.0&quot;</span><span class="p">,</span>
  <span class="nt">&quot;quasar-cli/vue-template-compiler&quot;</span><span class="p">:</span> <span class="s2">&quot;^2.6.0&quot;</span>
<span class="p">}</span>
</code></pre></div>


<p>然后运行 <code>yarn install</code> 命令即可。</p>
<h3>理解 Vue slot</h3>
<p>把 Vue 模板理解为渲染函数（<a href="https://vuejs.org/v2/guide/render-function.html#Template-Compilation">Vue 内部实现</a>就是把模板编译成渲染函数，因此这种相似是天然的），而不是 HTML。在这个类比下，slot 就是传递给子渲染函数的一个类型为渲染函数的参数，因此是高阶渲染函数。</p>
<p>在 Vue 子模板中，通过 <code>&lt;slot&gt;</code> 标签调用 slot 渲染函数，当存在多个 slot 的时候，用 <code>name</code> 属性进行区分，没有 <code>name</code> 属性的是 default slot。</p>
<div class="highlight"><pre><span></span><code><span class="nt">&lt;slot&gt;&lt;/slot&gt;</span>
</code></pre></div>


<p>相当于 <code>render(slots.default)</code>，</p>
<div class="highlight"><pre><span></span><code><span class="nt">&lt;slot</span> <span class="na">name=</span><span class="s">&quot;slot1&quot;</span><span class="nt">&gt;&lt;/slot&gt;</span>
</code></pre></div>


<p>相当于 <code>render(slots.slot1)</code>。</p>
<p>调用 slot 渲染函数的时候，通过 <code>v-bind</code> 指令把当前作用域中的变量传递给它。</p>
<div class="highlight"><pre><span></span><code><span class="nt">&lt;slot</span> <span class="na">:key=</span><span class="s">&quot;value&quot;</span><span class="nt">&gt;&lt;/slot&gt;</span>
</code></pre></div>


<p>相当于 <code>render(slots.default, { key: value })</code>。</p>
<p>在 Vue 父模板中，在子模板元素中定义和传递 default slot 渲染函数，非 default slot 需要定义在 <code>&lt;template&gt;</code> 元素中，用 <code>v-slot</code> 指令标记 slot 的名字和传递给 slot 的参数。</p>
<div class="highlight"><pre><span></span><code><span class="nt">&lt;child&gt;</span>
  <span class="nt">&lt;template</span> <span class="err">v-slot:slot1</span><span class="nt">&gt;</span>
    <span class="cp">{{</span> <span class="nv">key</span> <span class="cp">}}</span>
  <span class="nt">&lt;/template&gt;</span>
<span class="nt">&lt;/child&gt;</span>
</code></pre></div>


<p>相当于</p>
<div class="highlight"><pre><span></span><code><span class="kd">function</span> <span class="nx">slot1</span> <span class="p">()</span> <span class="p">{</span>
  <span class="nx">render</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span>
<span class="p">}</span>

<span class="nx">child</span><span class="p">({</span> <span class="nx">slot1</span> <span class="p">})</span>
</code></pre></div>


<p>如果需要使用传递给 slot 渲染函数的变量，也在 <code>v-slot</code> 中列出：</p>
<div class="highlight"><pre><span></span><code><span class="nt">&lt;child&gt;</span>
  <span class="nt">&lt;template</span> <span class="na">v-slot:slot1=</span><span class="s">&quot;{ key }&quot;</span><span class="nt">&gt;</span>
    <span class="cp">{{</span> <span class="nv">key</span> <span class="cp">}}</span>
  <span class="nt">&lt;/template&gt;</span>
<span class="nt">&lt;/child&gt;</span>
</code></pre></div>


<p>相当于</p>
<div class="highlight"><pre><span></span><code><span class="kd">function</span> <span class="nx">slot1</span> <span class="p">({</span> <span class="nx">key</span> <span class="p">})</span> <span class="p">{</span>
  <span class="nx">render</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span>
<span class="p">}</span>

<span class="nx">child</span><span class="p">({</span> <span class="nx">slot1</span> <span class="p">})</span>
</code></pre></div>


<p>在 Vue 2.6 之前，定义 slot 的时候，用 <code>slot</code> 和 <code>slot-scope</code> 两个属性标记 slot 的名称和传递给 slot 的参数。上述最后一个例子等价为：</p>
<div class="highlight"><pre><span></span><code><span class="nt">&lt;child&gt;</span>
  <span class="nt">&lt;template</span> <span class="na">slot=</span><span class="s">&quot;slot1&quot;</span> <span class="na">slot-scope=</span><span class="s">&quot;{ key }&quot;</span><span class="nt">&gt;</span>
    <span class="cp">{{</span> <span class="nv">key</span> <span class="cp">}}</span>
  <span class="nt">&lt;/template&gt;</span>
<span class="nt">&lt;/child&gt;</span>
</code></pre></div>


<p>slot 可以用于分离数据逻辑和渲染逻辑，见<a href="https://adamwathan.me/renderless-components-in-vuejs/">这篇文章</a>，也可以用<a href="https://medium.com/bethink-pl/higher-order-components-in-vue-js-a79951ac9176">高阶渲染组件（HOC）</a>和<a href="https://vuejs.org/v2/guide/mixins.html">混入</a>来实现。Vue 3计划添加<a href="https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md">基于函数的组件API</a>，为这类应用场景提供更好的支持。</p>
<h3>常见的 Windows 硬件信息查询命令</h3>
<ul>
<li>sysdm.cpl，系统属性</li>
<li>dxdiag，查询 DirectX 信息</li>
<li>msinfo32，系统信息</li>
<li>devmgmt.msc，设备管理器</li>
<li>taskmgr，任务管理器</li>
<li>winver，操作系统版本信息</li>
</ul>
<h3>扩展笔记本电脑内存</h3>
<p>操作系统是 Windows 10，在不安装外部硬件检测软件的条件下，可以用 wmic 命令查询购买何种型号的内存条的信息。</p>
<p>wmic memphysical 用于显示内存插槽的详细信息，要读懂其输出结果，<a href="https://docs.microsoft.com/en-us/windows/desktop/CIMWin32Prov/win32-physicalmemoryarray">这篇文档</a>必不可少。其输出的几个关键信息如下：</p>
<ul>
<li>MaxCapacity = MaxCapacityEx = 33554432，单位 KiB，该插槽最多支持32GiB 的内存（Windows 10 应该看 MaxCapacityEx 的值，参考<a href="https://superuser.com/a/1217045">关于这2个参数的讨论</a>）</li>
<li>MemoryDevices = 2，共2个内存插槽</li>
</ul>
<p>wmic memorychip 用于显示内存条的信息，每个内存条显示1条记录，只查到1条记录，说明电脑上还有一个空的内存插槽可用，其参数的含义可参考<a href="https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-physicalmemory">该文档</a>。关键参数：</p>
<ul>
<li>Capacity = 8589934592，单位为 Byte，该内存条为8GiB</li>
<li>ConfiguredClockSpeed = 2133，Speed = 2400，单位 MHZ，即该内存条的频率是 2400MHZ，实际频率 2133MHZ</li>
<li>DeviceLocator = DIMM 2，位于第2个插槽</li>
<li>FormFactor = 12，插槽类型是 SODIMM</li>
<li>ConfiguredVoltage = MinVoltage = MaxVoltage = 1200，单位 mV，电压1.2V</li>
<li>SMBIOSMemoryType = 26，该内存是 DDR4 内存</li>
</ul>
<p>因此，得到一些信息，电脑上理想情况下可以插2条16GiB 的内存，现在已经安装了一条8GiB 的内存，可以再安装一条16GiB 的 DDR4 SODIMM 内存，频率2400MHZ，电压1.2V。这些信息可以和相应型号笔记本电脑的规格说明中的内存信息作对比，后者主要用于增强对于通过软件方法获取的信息的信心。</p>
<h3>修改 quasar 项目的版本</h3>
<p>直接修改 package.json 会导致 package-lock.json 中的版本信息和 package.json 中的不一致，应该使用 <code>npm version</code> 命令：</p>
<div class="highlight"><pre><span></span><code>npm version  <span class="c1"># 列出当前项目的版本信息</span>
npm version patch  <span class="c1"># 更新 patch 版本</span>
npm version <span class="nv">$new_version</span>  <span class="c1"># 更新到新版本</span>
<span class="c1"># 在 git 项目中运行此命令，更新版本的同时 npm version 会创建 git commit 和 tag</span>
<span class="c1"># 用 --no-git-tag-version 关闭此行为</span>
npm version --no-git-tag-version <span class="nv">$args</span>
</code></pre></div>


<p>在编译 Cordova 应用之前，Quasar 会更新 Cordova config.xml 中的版本信息，其代码在 quasar-cli/lib/cordova/cordova-config.js 中：</p>
<div class="highlight"><pre><span></span><code><span class="nx">prepare</span> <span class="p">(</span><span class="nx">cfg</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">this</span><span class="p">.</span><span class="nx">doc</span> <span class="o">=</span> <span class="nx">et</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">filePath</span><span class="p">,</span> <span class="s1">&#39;utf-8&#39;</span><span class="p">))</span>
  <span class="k">this</span><span class="p">.</span><span class="nx">pkg</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="nx">appPaths</span><span class="p">.</span><span class="nx">resolve</span><span class="p">.</span><span class="nx">app</span><span class="p">(</span><span class="s1">&#39;package.json&#39;</span><span class="p">))</span>

  <span class="cm">/* other stuff */</span>
  <span class="nx">root</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s1">&#39;version&#39;</span><span class="p">,</span> <span class="nx">cfg</span><span class="p">.</span><span class="nx">cordova</span><span class="p">.</span><span class="nx">version</span> <span class="o">||</span> <span class="k">this</span><span class="p">.</span><span class="nx">pkg</span><span class="p">.</span><span class="nx">version</span><span class="p">)</span>

  <span class="k">this</span><span class="p">.</span><span class="nx">__save</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div>


<p>据此可以写自己的同步脚本：</p>
<div class="highlight"><pre><span></span><code><span class="c1">// tools/sync_cordova_version.js</span>

<span class="kr">const</span>
  <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;fs&#39;</span><span class="p">),</span>
  <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;path&#39;</span><span class="p">),</span>
  <span class="nx">et</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;elementtree&#39;</span><span class="p">)</span>

<span class="kr">const</span> <span class="nx">filePath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="s1">&#39;../src-cordova/config.xml&#39;</span><span class="p">)</span>
<span class="kr">const</span> <span class="nx">doc</span> <span class="o">=</span> <span class="nx">et</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">filePath</span><span class="p">,</span> <span class="s1">&#39;utf-8&#39;</span><span class="p">))</span>
<span class="kr">const</span> <span class="nx">pkg</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;../package.json&#39;</span><span class="p">)</span>
<span class="kr">const</span> <span class="nx">root</span> <span class="o">=</span> <span class="nx">doc</span><span class="p">.</span><span class="nx">getroot</span><span class="p">()</span>
<span class="nx">root</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s1">&#39;version&#39;</span><span class="p">,</span> <span class="nx">pkg</span><span class="p">.</span><span class="nx">version</span><span class="p">)</span>
<span class="kr">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="nx">doc</span><span class="p">.</span><span class="nx">write</span><span class="p">({</span> <span class="nx">indent</span><span class="o">:</span> <span class="mi">4</span> <span class="p">})</span>
<span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="nx">filePath</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="s1">&#39;utf8&#39;</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Updated Cordova config.xml&#39;</span><span class="p">)</span>
</code></pre></div>


<h3>Axios 在 HTTP 请求中附加验证信息</h3>
<p>正确的做法是<a href="https://github.com/axios/axios#custom-instance-defaults">设置 axios 实例的默认参数</a>：</p>
<div class="highlight"><pre><span></span><code><span class="nx">axiosInstance</span><span class="p">.</span><span class="nx">defaults</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">common</span><span class="p">.</span><span class="nx">Authorization</span> <span class="o">=</span> <span class="s1">&#39;Token &#39;</span> <span class="o">+</span> <span class="nx">token</span>
</code></pre></div>


<p>设置 <a href="https://github.com/axios/axios#interceptors">axios 实例的拦截器</a>应该也有效果，但没有测试过，设置 axios 全局拦截器肯定没有效果，因为 <a href="https://github.com/axios/axios/issues/993">axios 实例不能调用全局拦截器</a>。</p>
<h3>程序退出状态码（Exit Status）</h3>
<p>当程序需要作为子进程运行时，可以用退出状态码表示其退出状态，但只是子进程和父进程之间约定，如何定义状态码最终取决于父进程将如何使用它们。以下内容仅供参考：</p>
<ul>
<li>
<p>最常见的是用0表示程序运行成功，1表示程序运行失败，这是 <a href="https://www.gnu.org/software/libc/manual/html_node/Exit-Status.html">POSIX 约定</a>。</p>
</li>
<li>
<p><a href="http://tldp.org/LDP/abs/html/exitcodes.html">BASH 的约定</a>更加详细。</p>
</li>
<li>
<p>Python <code>sys.exit()</code> 的<a href="https://docs.python.org/3/library/sys.html#sys.exit">文档</a>，以及 os 模块定义的<a href="https://docs.python.org/3/library/os.html#os.EX_OK">退出状态码</a>。</p>
</li>
</ul>
<h3>Cordova Android 版本代码</h3>
<p>如果没有设置 <code>android-versionCode</code>，Cordova 会根据 <code>version</code> 来计算：</p>
<div class="highlight"><pre><span></span><code><span class="err">versionCode = MAJOR * 10000 + MINOR * 100 + PATCH</span>
</code></pre></div>


<p>但这并非编译得到的 Android apk 文件中的版本代码，设置 <code>cdvBuildMultipleApks</code> 会把以上设置的或计算得到的版本代码乘10，最后一位用来表示目标平台的架构。如果需要描述 apk 文件的版本代码，应该直接从 apk 文件读取：</p>
<div class="highlight"><pre><span></span><code>aapt dump badging <span class="nv">$apk_file</span>
</code></pre></div>


<h3>Cordova 拍摄和上传视频</h3>
<p>安装和 Cordova 版本匹配的 cordova-plugin-media-capture cordova-plugin-file 插件：</p>
<div class="highlight"><pre><span></span><code>cordova plugin add cordova-plugin-media-capture
cordova plugin add cordova-plugin-file
</code></pre></div>


<p>拍摄视频</p>
<div class="highlight"><pre><span></span><code><span class="kr">const</span> <span class="nx">captureSuccess</span> <span class="o">=</span> <span class="p">(</span><span class="nx">mediaFiles</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="nx">path</span> <span class="o">=</span> <span class="nx">mediaFiles</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">fullPath</span>
  <span class="nx">dealWith</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span>
<span class="p">};</span>

<span class="kr">const</span> <span class="nx">captureError</span> <span class="o">=</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span>
<span class="p">};</span>

<span class="nx">navigator</span><span class="p">.</span><span class="nx">device</span><span class="p">.</span><span class="nx">capture</span><span class="p">.</span><span class="nx">captureVideo</span><span class="p">(</span><span class="nx">captureSuccess</span><span class="p">,</span> <span class="nx">captureError</span><span class="p">,</span> <span class="p">{</span> <span class="nx">limit</span><span class="o">:</span> <span class="mi">1</span> <span class="p">})</span>
</code></pre></div>


<p>这里 <code>mediaFiles[0].fullPath</code> 是以 <code>file:///</code> 开头的 URL，用 <code>resolveLocalFileSystemURL</code>（由 cordova-plugin-file 提供 API 支持）获取文件对象：</p>
<div class="highlight"><pre><span></span><code><span class="kr">const</span> <span class="nx">getLocalFile</span> <span class="o">=</span> <span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">onSuccess</span><span class="p">,</span> <span class="nx">onError</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="cm">/* global resolveLocalFileSystemURL */</span>
  <span class="nx">resolveLocalFileSystemURL</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">url</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span> <span class="p">(</span><span class="nx">entry</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
    <span class="nx">entry</span><span class="p">.</span><span class="nx">file</span><span class="p">((</span><span class="nx">file</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
      <span class="kr">const</span> <span class="nx">fileType</span> <span class="o">=</span> <span class="nx">file</span><span class="p">.</span><span class="nx">type</span>
      <span class="kr">const</span> <span class="nx">fileReader</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FileReader</span><span class="p">()</span>
      <span class="nx">fileReader</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
        <span class="nx">onSuccess</span><span class="p">(</span><span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="k">new</span> <span class="nx">Uint8Array</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">result</span><span class="p">)],</span> <span class="nx">fileType</span><span class="p">))</span>
      <span class="p">}</span>
      <span class="nx">fileReader</span><span class="p">.</span><span class="nx">onerror</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
        <span class="nx">onError</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">error</span><span class="p">)</span>
      <span class="p">}</span>
      <span class="nx">fileReader</span><span class="p">.</span><span class="nx">readAsArrayBuffer</span><span class="p">(</span><span class="nx">file</span><span class="p">)</span>
    <span class="p">},</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">onError</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">})</span>
  <span class="p">},</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">onError</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">})</span>
<span class="p">}</span>
</code></pre></div>


<p>其中 <code>onSuccess</code> 以 FormData 的方式上传文件：</p>
<div class="highlight"><pre><span></span><code><span class="kr">const</span> <span class="nx">uploadVideo</span> <span class="o">=</span> <span class="p">(</span><span class="nx">file</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="kr">const</span> <span class="nx">formData</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FormData</span><span class="p">()</span>
  <span class="nx">formData</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s1">&#39;file&#39;</span><span class="p">,</span> <span class="nx">file</span><span class="p">)</span>
  <span class="k">return</span> <span class="nx">axios</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="nx">postURL</span><span class="p">,</span> <span class="nx">formData</span><span class="p">,</span> <span class="p">{</span>
    <span class="nx">headers</span><span class="o">:</span> <span class="p">{</span>
      <span class="s1">&#39;Content-Type&#39;</span><span class="o">:</span> <span class="s1">&#39;multipart/form-data&#39;</span>
    <span class="p">}</span>
  <span class="p">})</span>
<span class="p">}</span>
</code></pre></div>


<p>如果 <code>resolveLocalFileSystemURL</code> 调用失败，<code>onError</code> 会让用户选择需要上传的视频：</p>
<div class="highlight"><pre><span></span><code><span class="kr">const</span> <span class="nx">onError</span> <span class="o">=</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;Can not get File object from URL：&#39;</span> <span class="o">+</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">err</span><span class="p">))</span>
  <span class="nx">videoFileInput</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;change&#39;</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
    <span class="nx">uploadVideo</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">files</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
  <span class="p">})</span>
  <span class="nx">videoFileInput</span><span class="p">.</span><span class="nx">click</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div>


<p>其中 <code>videoFileInput</code> 是 DOM 对象：</p>
<div class="highlight"><pre><span></span><code><span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&quot;file&quot;</span> <span class="na">style</span><span class="o">=</span><span class="s">&quot;opacity:0&quot;</span><span class="p">&gt;</span>
</code></pre></div>


<p>扩展阅读：</p>
<ul>
<li><a href="https://cordova.apache.org/blog/2017/10/18/from-filetransfer-to-xhr2.html">Cordova 用 XHR 上传本地文件</a></li>
<li><a href="https://developers.google.com/web/updates/2011/08/Seek-into-local-files-with-the-File-System-API">File 对象的 Seek 操作</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file">&lt;input type="file"&gt;</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications">Using files from web applications</a></li>
</ul>
<h3>Cordova 开发环境变量设置</h3>
<p>Android 编译及其它工具</p>
<div class="highlight"><pre><span></span><code><span class="nb">export</span> <span class="nv">ANDROID_HOME</span><span class="o">=</span><span class="nv">$HOME</span>/Android/Sdk

<span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$ANDROID_HOME</span>/tools:<span class="nv">$PATH</span>
<span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$ANDROID_HOME</span>/platform-tools:<span class="nv">$PATH</span>
<span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$ANDROID_HOME</span>/build-tools/28.0.3:<span class="nv">$PATH</span>
<span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:<span class="nv">$HOME</span>/gradle-5.1/bin
</code></pre></div>


<p>node</p>
<div class="highlight"><pre><span></span><code><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:<span class="nv">$HOME</span>/node/bin
</code></pre></div>


<h3>修改 Linux 用户名</h3>
<p>重命名登录名，该步骤会处理 /etc/passwd、/etc/shadow、/etc/group、/etc/gshadow 中的用户名的替换，但不会处理 /etc/subuid 和 /etc/subgid：</p>
<div class="highlight"><pre><span></span><code><span class="err">usermod -l newname oldname</span>
</code></pre></div>


<p>移动用户主目录：</p>
<div class="highlight"><pre><span></span><code><span class="err">usermod -d newhome username</span>
</code></pre></div>


<p>需要注意的是，系统中涉及用户名的配置覆盖面可能非常广，基于用户主目录的配置和缓存文件也非常多，要把它们全部找出来几乎是不可能的事，修改用户名和用户主目录就要做好系统中出现各种问题的心理准备。可以排查一下常见的地方：</p>
<ul>
<li>sudo 配置 /etc/sudoers</li>
<li>登录管理器配置，如 lightdm：/etc/lightdm/lightdm.conf</li>
<li>用户配置 .profile 等里面是否有硬编码的主目录路径，如果有，替换成 <code>$HOME</code></li>
</ul>
<h3>修改 Linux hostname</h3>
<p>Systemd 的 <code>hostnamectl set-hostname new_hostname</code> 差不多等价于 <code>hostname new_hostname</code> 和编辑 /etc/hostname。除此之外，还需要编辑 /etc/hosts，确保新的 hostname 映射到正确的 IP 地址。</p>
<h3>HTTP POST 参数在请求的哪个部分</h3>
<p>POST 参数<a href="https://stackoverflow.com/a/612035/9634290">可以放在 URL 里面</a>，但数据应该放在请求的 Body 里，其类型可能是 <code>application/x-www-form-urlencoded</code>、<code>application/json</code> 或者 <code>multipart/form-data</code> 等其它格式，如果是 <code>application/x-www-form-urlencoded</code>，则和 query parameter 差不多，如下所示：</p>
<div class="highlight"><pre><span></span><code><span class="nf">POST</span> <span class="nn">/path/script.cgi</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.0</span>
<span class="na">From</span><span class="o">:</span> <span class="l">frog@jmarshall.com</span>
<span class="na">User-Agent</span><span class="o">:</span> <span class="l">HTTPTool/1.0</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/x-www-form-urlencoded</span>
<span class="na">Content-Length</span><span class="o">:</span> <span class="l">32</span>

home=Cosby&amp;favorite+flavor=flies
</code></pre></div>


<p>见<a href="https://stackoverflow.com/a/612035/9634290">这篇回答</a>。</p>
<h3>模拟 Django migration</h3>
<p>如果外部应用修改了 Django 使用的数据表的结构，修改了 Django 应用的模型代码以反映其变化，运行 <code>makemigrations</code> 之后不要运行 <code>migrate</code> 命令，而是手动在 <code>django_migrations</code> 表中插入一行记录，让 Django 认为该数据库迁移已经运行了，如下所示：</p>
<div class="highlight"><pre><span></span><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="o">`</span><span class="n">django_migrations</span><span class="o">`</span> <span class="p">(</span><span class="o">`</span><span class="n">app</span><span class="o">`</span><span class="p">,</span> <span class="o">`</span><span class="n">name</span><span class="o">`</span><span class="p">,</span> <span class="o">`</span><span class="n">applied</span><span class="o">`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">&#39;api&#39;</span><span class="p">,</span> <span class="s1">&#39;0003_auto_20190524_0716&#39;</span><span class="p">,</span> <span class="s1">&#39;2019-05-24 07:18:00&#39;</span><span class="p">);</span>
</code></pre></div>


<p>这样 Django 既不会抱怨有数据库迁移没有运行，也避免了 Django 修改数据表可能引起的问题。</p>
<h3>Linux fontconfig 常用命令</h3>
<div class="highlight"><pre><span></span><code>fc-list  <span class="c1"># 列出系统中的所有字体</span>
fc-list -f <span class="s1">&#39;%{file}\n&#39;</span> :lang<span class="o">=</span>zh  <span class="c1"># 列出系统中的所有中文字体文件</span>
fc-match -s monospace:charset<span class="o">=</span>1F4A9  <span class="c1"># 列出 Unicode 字符为1F4A9 匹配的所有等宽字体</span>
</code></pre></div>


<h3>pandoc docx 文档模板</h3>
<p>使用 pandoc 把文档转化成 docx 格式的时候，可以使用 --reference-docx 指定模板文档，通过修改模板文档的样式改变输出的 docx 文档的样式。下面的描述和<a href="https://pandoc.org/MANUAL.html">官方文档</a>（见 --reference-doc=FILE 部分）中的说法不一致，有可能是因为我使用的 pandoc 是 1.19.2.4（没有找到文档），而官方文档是 2.x 版的。</p>
<p>生成 reference.docx：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># -o 参数没有作用，必须重定向，这一点和 manpage 里的描述也不一致</span>
pandoc --print-default-data-file reference.docx &gt;custom-reference.docx
</code></pre></div>


<p>打开 custom-reference.docx 修改文档中各类内容（如标题1、标题2、正文、超链接、表格）的样式，不是文档的内容，不清楚的参考 <a href="https://support.office.com/en-us/article/Customize-or-create-new-styles-in-Word-d38d6e47-f6fc-48eb-a607-1eb120dec563">Word 官方文档</a>。在 pandoc 命令行加上 --reference-docx=custom-reference.docx 就可以了。</p>
<h3>npm random 软件包需要浏览器支持 ES6 Proxy 特性</h3>
<p>random 软件包的依赖包 ow-lite 使用了 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">ES6 Proxy 特性</a>，该特性无法 polyfill，需要浏览器支持。</p>
<h3>Pelican 小结</h3>
<p>如果站点的页面较多，Pelican 的 <a href="https://github.com/akhayyat/pelican-page-hierarchy/">page hierarchy</a> 插件可以使生成的 pages 页面不维持其源文件的文件结构，并且支持父页面和子页面功能。用 Google 搜索“关键字 site:站点名”或者 <code>git grep 源码目录</code> 要比自己在站点上的 JavaScript 插件靠谱，因此这里基本没有 Sphinx 的用武之地。</p>
<p>所有的文章的 URL 都是 posts/&lt;slug&gt; 的形式，如果有两篇文章的 slug 碰巧相同导致生成的 HTML 文件会互相覆盖，pelican 会给出警告信息。但如果文章和页面的 HTML 文件会互相覆盖，pelican 不会提示任何信息，因此所有页面的 URL 都是 pages/&lt;path&gt;/&lt;to&gt;/&lt;page&gt;，和文章的根路径隔开。</p>
<h3>Python 软件包管理</h3>
<p>从 setuptools 到 easy_install 一直到 pip，都没有像样的软件包依赖关系的管理，直到 <a href="https://pypi.org/project/pipenv/">pipenv</a> 的出现，其依赖关系的管理才赶上 npm。pipenv 创建依赖关系的时候好像会<a href="https://github.com/pypa/pipenv/issues/356#issuecomment-305173170">把所有的软件包检查下载一遍</a>，导致这个过程非常慢，而且没有用户反馈，因此用 pipenv 安装软件包的时候最好使用<a href="https://mirrors.tuna.tsinghua.edu.cn/help/pypi/">国内的 pypi 镜像</a>，万一卡了基本可以排除网络原因，最重要的是不使用镜像的时候我从来没有等到 pipenv 命令运行结束。</p>
<p>软件包依赖管理的一个重要需求是查询软件包之间的依赖关系，可以通过 <code>pipenv graph</code> 命令来实现或者使用 <a href="https://github.com/naiquevin/pipdeptree">pipdeptree</a>。</p>
<p>在 Python 软件包管理的问题上，<a href="https://pypa.io/">PYPA</a>写了一个<a href="https://packaging.python.org/">指南</a>，建议阅读。</p>
<p><a href="https://setuptools.readthedocs.io/en/latest/setuptools.html#development-mode">Development mode</a> 是一种把正在开发的软件包安装到 Python 运行环境中的技术，如果有多个存在依赖环境的 Python 软件包在开发中，这种方式可以使下游软件包实时看到上游的软件包的改动，也可以用于消除同一个软件包中的 test 和 lib 模块之间的路径依赖。</p>
<h3>Linux 测试服务安装小结</h3>
<p>Docker 的版本有些混乱，<a href="https://www.quora.com/How-did-Docker-go-from-version-1-x-to-17-x">从 1.x 直接变成
17.x</a>，在<a href="https://docs.docker.com/engine/release-notes/">官方文档</a>中就找不到
17.x
之前的历史了，其实他们只是切换到<a href="https://blog.docker.com/2017/03/docker-enterprise-edition/">基于年份的版本号</a>，<a href="https://docs.docker.com/install/linux/docker-ce/ubuntu/">官方文档</a>建议删除其它版本的
docker，安装 docker-ce，个人认为发行版自带的 docker
用得挺好，没有必要多此一举。</p>
<p>CentOS 软件源如 <a href="https://fedoraproject.org/wiki/EPEL">EPEL</a> 可以直接从
yum 安装，因为 yum install 支持 URL 形式的参数。</p>
<div class="highlight"><pre><span></span><code><span class="err">yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm</span>
<span class="err">yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm</span>
</code></pre></div>


<p><code>yum whatprovides &lt;path&gt;</code>
可以用来哪个软件包中存在指定的路径，支持通配符，例如下面的命令可以查到
yumdb 命令来自于 yum-utils 软件包。</p>
<div class="highlight"><pre><span></span><code><span class="err">yum whatprovides &#39;*bin/yumdb&#39;</span>
</code></pre></div>


<p>但是这个命令搜索的是软件源中所有的软件包，而非系统中已经安装的软件包，例如
<code>yum whatprovides $(which nginx)</code>，能查到 nginx
包中包含该路径，但也许系统中的 nginx
是用户自己编译安装的，要确切的查询系统中的 nginx
来自于哪个已安装的包，应该使用 <code>rpm -qf $(which nginx)</code>。</p>
<p>和
<code>rpm -qf</code>相反，<code>rpm -ql &lt;package&gt;</code>用来列出系统中已安装的软件包中的所有文件。要列出没有安装的软件包中的内容，需要使用
yum-utils 的 repoquery 命令，或者 dnf：</p>
<div class="highlight"><pre><span></span><code><span class="err">repoquery -l time</span>
<span class="err">dnf repoquery -l time</span>
</code></pre></div>


<p><code>yum list &lt;package&gt;</code>用来列出软件包的信息，如果软件包已经被安装，则还会出现在
Installed Packages 中，否则会出现在Available Packages
中，如果软件包不存在，会显示 No matching Packages to list。加入
<code>--showduplicates</code> 参数后，该命令会列出启用的软件源中所有版本的软件包。</p>
<div class="highlight"><pre><span></span><code><span class="err">yum list --show-duplicates docker</span>
</code></pre></div>


<p>会显示：</p>
<div class="highlight"><pre><span></span><code><span class="err">docker.x86_64  2:1.13.1-75.git8633870.el7.centos  extras</span>
<span class="err">docker.x86_64  2:1.13.1-84.git07f3374.el7.centos  extras</span>
<span class="err">...</span>
<span class="err">docker.x86_64  2:1.13.1-96.gitb2f74b2.el7.centos  extras</span>
</code></pre></div>


<p>然后用户可以输入命令选择安装非最新版本的软件包：</p>
<div class="highlight"><pre><span></span><code><span class="err">yum install docker-1.13.1-84.git07f3374.el7.centos</span>
</code></pre></div>


<p>注意这里的软件版本既不包括第1列 docker 后面的 CPU 架构
<code>.x86_64</code>，也不包括第2列版本信息最前面的 <code>2:</code>。</p>
<p>Oracle JDK 和 OpenJDK
有什么区别？根据<a href="https://www.baeldung.com/oracle-jdk-vs-openjdk">这篇文章</a>，Oracle
JDK 基于 OpenJDK，Oracle JDK 比 OpenJDK
性能好，至于兼容性，两者<a href="https://stackoverflow.com/a/38685948/9634290">都通过了 TCK
验证</a>，应该不会有问题。</p>
<p>禅道官方提供的 rpm 安装包和 CentoOS
7的系统文件存在路径冲突，并且它依赖于 Apache
httpd，其实未必，个人偏好<a href="https://www.zentao.net/book/zentaopmshelp/101.html">源码安装</a>，然后像其它
php 应用（如 wordpress）一样配置。</p>
<p>安装扩展模块的时候，可以用 <code>php -m</code>
命令列出当前系统中已存在的模块，因为有的模块已经编译进 php
中，不需要再找安装包。</p>
<h3>HTML 表格内文字换行</h3>
<p>如果表格内某列有很长的文字，但不希望该列占用太多宽度，以免把后面的列挤到页面之外，可以指定该列文字换行：</p>
<div class="highlight"><pre><span></span><code><span class="nt">white-space</span><span class="o">:</span> <span class="nt">normal</span><span class="o">;</span>
</code></pre></div>


<p><a href="https://stackoverflow.com/a/5108367/9634290">这篇回答</a>给出了兼容旧浏览器的解决方案：</p>
<div class="highlight"><pre><span></span><code><span class="p">.</span><span class="nc">wrapword</span> <span class="p">{</span>
  <span class="k">white-space</span><span class="p">:</span> <span class="bp">-moz-</span><span class="kc">pre-wrap</span> <span class="cp">!important</span><span class="p">;</span>  <span class="c">/* Mozilla, since 1999 */</span>
  <span class="k">white-space</span><span class="p">:</span> <span class="bp">-webkit-</span><span class="kc">pre-wrap</span><span class="p">;</span> <span class="c">/*Chrome &amp; Safari */</span>
  <span class="k">white-space</span><span class="p">:</span> <span class="o">-</span><span class="kc">pre-wrap</span><span class="p">;</span>      <span class="c">/* Opera 4-6 */</span>
  <span class="k">white-space</span><span class="p">:</span> <span class="bp">-o-</span><span class="kc">pre-wrap</span><span class="p">;</span>    <span class="c">/* Opera 7 */</span>
  <span class="k">white-space</span><span class="p">:</span> <span class="kc">pre-wrap</span><span class="p">;</span>       <span class="c">/* css-3 */</span>
  <span class="k">word-wrap</span><span class="p">:</span> <span class="kc">break-word</span><span class="p">;</span>       <span class="c">/* Internet Explorer 5.5+ */</span>
  <span class="k">word-break</span><span class="p">:</span> <span class="n">break-all</span><span class="p">;</span>
  <span class="k">white-space</span><span class="p">:</span> <span class="kc">normal</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>


<div class="highlight"><pre><span></span><code><span class="p">&lt;</span><span class="nt">table</span> <span class="na">style</span><span class="o">=</span><span class="s">&quot;table-layout:fixed; width:400px&quot;</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">td</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;wrapword&quot;</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">table</span><span class="p">&gt;</span>
</code></pre></div>


<h3>Python 3 str 和 bytes 类型互相转化</h3>
<p>通过 <code>str.encode()</code> 和 <code>bytes.decode()</code>。</p>
<h3>Node.js 只有异步代码运行时会不会退出</h3>
<p>取决于是否有定时器、异步 IO 在运行，如果有，node.js
不会退出，否则即使有异步代码运行，node.js 也会退出。见<a href="https://github.com/nodejs/node/issues/22088">这个
issue</a>，<a href="https://stackoverflow.com/a/46967155/9634290">这篇回答</a>和<a href="https://stackoverflow.com/a/46916601/9634290">这篇回答</a>。</p>
<h3><a href="https://router.vuejs.org/guide/essentials/navigation.html">Vue router 导航</a></h3>
<p>此处共有3个
API：<a href="https://router.vuejs.org/api/#router-push"><code>router.push()</code></a>、<a href="https://router.vuejs.org/api/#router-replace"><code>router.replace()</code></a>
和
<a href="https://router.vuejs.org/api/#router-go"><code>router.go()</code></a>，这3个函数分别对应于
<code>window.history.pushState()</code>、<code>window.history.replaceState()</code> 和
<code>window.history.go()</code>。</p>
<p><code>router.go()</code> 和另外两个 API
有显著的差别，即后者会改变浏览器的历史栈，前者不会，它仅在浏览器的历史栈中前后移动。其参数也有显著的差异，<code>router.push()</code>
和 <code>router.replace()</code> 第一个参数是目标位置的确切信息，<code>router.go()</code>
的唯一一个参数是目标栈相对于当前栈的位移。</p>
<p>当然还有2个
API：<a href="https://router.vuejs.org/api/#router-back"><code>router.back()</code></a> 和
<a href="https://router.vuejs.org/api/#router-forward"><code>router.forward()</code></a>，它们其实是
<code>router.go(-1)</code> 和 <code>router.go(1)</code> 更友好的名字，对应于 <code>window.history.back()</code>
和
<code>window.history.forward()</code>，或者浏览器的“后退”和“前进”按钮。只要熟悉<a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API">浏览器历史操作
API</a>，理解上述内容便是小菜一碟。</p>
<h3>Django 修改用户密码</h3>
<p>下面是错误的做法：</p>
<div class="highlight"><pre><span></span><code><span class="n">user</span> <span class="o">=</span> <span class="n">some_way_to_get_user</span><span class="p">()</span>
<span class="n">user</span><span class="o">.</span><span class="n">password</span> <span class="o">=</span> <span class="n">new_password</span>
<span class="n">user</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
</code></pre></div>


<p>上面的做法会把 <code>new\_password</code> 明文存储在数据库中，但是 Django
<a href="https://docs.djangoproject.com/en/2.2/topics/auth/passwords/#how-django-stores-passwords">不明文存储用户密码</a>。这里要把
<code>new\_password</code> 转化成 Django 支持的存储格式，因此<a href="https://docs.djangoproject.com/en/2.2/topics/auth/default/#changing-passwords">必须调用
set_password()</a>：</p>
<div class="highlight"><pre><span></span><code><span class="n">user</span> <span class="o">=</span> <span class="n">some_way_to_get_user</span><span class="p">()</span>
<span class="n">user</span><span class="o">.</span><span class="n">set_password</span><span class="p">(</span><span class="n">new_password</span><span class="p">)</span>
<span class="n">user</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
</code></pre></div>


<p>产生这一错误印象的源头可能是 Django 在创建用户时，通过 password
参数传递密码：</p>
<div class="highlight"><pre><span></span><code><span class="n">User</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create_user</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="n">password</span><span class="p">)</span>
</code></pre></div>


<p>注意这并不等同于 <code>user.password = password</code>。</p>
<p>如果对密码存储和用户验证有更高的要求，建议阅读：<a href="https://docs.djangoproject.com/en/2.2/topics/auth/passwords/#password-upgrading">更新用户密码的存储方式</a>，<a href="https://www.django-rest-framework.org/api-guide/authentication/#custom-authentication">DRF
自定义用户验证</a>，<a href="https://docs.djangoproject.com/en/2.2/topics/auth/default/#authenticating-users">Django
用户验证</a>。</p>
<h3>开源简谱输入方案</h3>
<p>剑桥大学的 <a href="http://people.ds.cam.ac.uk/ssb22/">Silas S. Brown</a>
写了一个将简谱记号转化成 <a href="http://lilypond.org">LilyPond</a> 输入文件的
<a href="https://github.com/ssb22/jianpu-ly">Python
脚本</a>，并且写了一个<a href="http://people.ds.cam.ac.uk/ssb22/mwrhome/jianpu-ly.html">介绍网页</a>，国内的<a href="http://www.cnblogs.com/quantumman/p/5189701.html">皮波迪网友</a>写了<a href="http://www.cnblogs.com/quantumman/p/5189701.html">中文介绍</a>，并且提供了一个很好的例子，对照这个例子和用法的介绍，即使像我这样没有音乐基础的人，也能输入简单的乐谱。</p>
<p>用这种方法生成的简谱，拍子是放在音符的前面，并且和音符位于同一行，有时候我们需要把拍子和大调放在同一行，在新行开始音符，这时可以修改生成的
LilyPond 输入文件，在 <code>\\time</code>
命令的同一段中添加下面的命令<a href="http://lilypond.org/doc/v2.18/Documentation/notation/visibility-of-objects#removing-the-stencil">隐藏</a>音符前面的拍子：</p>
<div class="highlight"><pre><span></span><code><span class="err">\omit Staff.TimeSignature</span>
</code></pre></div>


<p>把 <code>\\mark</code> 命令改为：</p>
<div class="highlight"><pre><span></span><code><span class="err">\mark \markup {</span>
<span class="err">    \pad-around #3 \line {</span>
<span class="err">        1=D</span>
<span class="err">        \hspace #1</span>
<span class="err">        \raise #1.0 \number \fontsize #-1 \column {</span>
<span class="err">            \override #&#39;(thickness . 2) \underline 4</span>
<span class="err">            4</span>
<span class="err">        }</span>
<span class="err">    }</span>
<span class="err">}</span>
</code></pre></div>


<p>这一段代码需要参考的文档有：<a href="http://lilypond.org/doc/v2.18/Documentation/notation/writing-text#text-marks"><code>\\mark</code></a>、<a href="http://lilypond.org/doc/v2.18/Documentation/notation/formatting-text#graphic-notation-inside-markup"><code>\\pad-around</code></a>、<a href="http://lilypond.org/doc/v2.18/Documentation/notation/align"><code>\\line</code>、<code>\\hspace</code>、<code>\\raise</code>、<code>\\column</code></a>、<a href="http://lilypond.org/doc/v2.18/Documentation/notation/font"><code>\\number</code>、<code>\\fontsize</code>、<code>\\underline</code></a>。</p>
<p>这里给标注加了
padding，但所有音符之间的行距都会加大，可能所有音符本质上是同一行，只是因为排版的原因被分成了多行。</p>
<h3>JavaScript 数组排序</h3>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort"><code>Array.prototype.sort()</code></a>
在不提供 <code>compareFunction</code>
参数的情况下，把数组中的每一个元素转化成字符串，然后按照字符串 UTF-16
编码升序排列（<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Description">除了
undefined</a>）。如果需要降序排列，可以结合
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse"><code>Array.prototype.reverse()</code></a>，或者提供
<code>compareFunction</code> 参数。</p>
<p>如果提供 <code>compareFunction</code> 参数，<code>compareFunction(a, b)</code> 必须返回 3
种数值，如果 <code>a</code> 在 <code>b</code> 之后，返回大于0的值，如果 <code>a</code> 在 <code>b</code>
之前，返回小于0的值，如果 <code>a</code> 和 <code>b</code>
的次序相同，返回0。我<a href="https://codepen.io/jpli/pen/ZNWNKa">搞错</a>过，<a href="https://stackoverflow.com/search?q=Array.prototype.sort">很多其他人</a>也搞错了。因为排序的重要性，贴出几篇值得参考的回答（<code>[(a, b) =&amp;gt; (a&amp;lt;b)-(b&amp;lt;a)</code>](https://stackoverflow.com/a/39751889/9634290)，<a href="https://stackoverflow.com/a/24080786/9634290"><code>compareFunction</code>
详解</a>）。</p>
<p>总之，如果 <code>compareFunction</code> 不能正常工作，就不要指望
<code>Array.prototype.sort()</code> 能按照用户的意愿把数组排序好，尤其是
<code>compareFunction</code> 可能遇到本质上无法排序的参数，如
<code>undefined</code>，<code>NaN</code>，如果数组会包含这样的值，写出能正常工作的 <code>compareFunction</code>
可以算作一项挑战。</p>
<p>支持该特性的浏览器都遵循规范，但规范中并没有规定次序相同的元素之间的顺序必须如何，因此即使
<code>compareFunction</code> 可以正常工作，不同浏览器如 Firefox 和 Chrome
排序的结果仍可能有差异，甚至同一浏览器（Chrome）对不同长度（10个元素以内和10个元素以上）的数组的排序结果看起来都不一样。参见<a href="https://stackoverflow.com/a/44120478/9634290">stable
sort and unstable sort</a>。</p>
<p>这个函数会改变原来的 array，如果不想改变原来的 array，先调用
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice"><code>Array.prototype.slice()</code></a>
复制 array 再排序。</p>
<p>很多情况下，数组元素的排序取决于元素经过计算得到的有序值，这种计算称为
<code>keyFunction</code>，lodash 的 <a href="https://lodash.com/docs/4.17.11#sortBy"><code>sortBy()</code>
函数</a>要比 <code>Array.prototype.sort()</code>
更适合这种用途。</p>
<h3>清空 Django 数据表</h3>
<ul>
<li>清空表格：<code>Model.objects.all().delete()</code>，见<a href="https://stackoverflow.com/a/4532693/9634290">这篇回答</a></li>
<li>清空所有表格：<code>python manage.py flush</code>，见<a href="https://stackoverflow.com/a/6485171/9634290">这篇回答</a></li>
</ul>
<h3>CSS 封装</h3>
<ul>
<li><a href="https://vue-loader.vuejs.org/guide/css-modules.html">Vue SFC CSS
    modules</a>，<a href="https://vue-loader.vuejs.org/guide/scoped-css.html">Scoped
    CSS</a>
    是另一种实现思路，比较一下。</li>
<li><a href="https://github.com/css-modules/css-modules">CSS modules 文档</a></li>
</ul>
<h3>构建 JavaScript 对象</h3>
<p>一个 JavaScript 对象可以看作一组键值映射，一个 JavaScript
对象中的所有键都是互不相同的字符串。写成代码的形式就是3条规则：</p>
<ul>
<li>用引号 : 把键和值隔开</li>
<li>用逗号 , 把键值对隔开</li>
<li>用弯括号 {} 把所有键值对括起来</li>
</ul>
<p>例如：</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span>
    <span class="s1">&#39;a&#39;</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
    <span class="s1">&#39;11&#39;</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
    <span class="s1">&#39;011&#39;</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span>
    <span class="s1">&#39;x//y&#39;</span><span class="o">:</span> <span class="mi">4</span><span class="p">,</span>
    <span class="s1">&#39;3.142&#39;</span><span class="o">:</span> <span class="mi">5</span>
<span class="p">}</span>
</code></pre></div>


<p>在这里，因为键 <code>'a'</code> 的名称是合法的变量名，其引号可以省略，键 <code>'11'</code> 和键
<code>'3.142'</code> 可以由数值11和3.142转化成字符串得到，也可以写成数值的形式，而键
<code>'011'</code> 和 <code>'x//y'</code>
其名称不是合法的变量名，也不能由任何数值转化成字符串得到，必须写成字符串的形式，因此上述表达式等价于：</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span>
    <span class="nx">a</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
    <span class="mi">11</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
    <span class="s1">&#39;011&#39;</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span>
    <span class="s1">&#39;x//y&#39;</span><span class="o">:</span> <span class="mi">4</span><span class="p">,</span>
    <span class="mf">3.142</span><span class="o">:</span> <span class="mi">5</span>
<span class="p">}</span>
</code></pre></div>


<p>es6
在键中引入了变量名的简写形式，如果键的名称是当前作用域中的变量，并且对应的值恰好也是该变量的值，该键值对就可以直接写成变量名，即把
<code>name: name</code> 简写成 <code>name</code>。</p>
<p>es6 还允许通过计算生成的键名，只要用方括号 <code>\[\]</code>
把相应的表达式括起来，放在键的位置上。即用定义对象的时候直接输入表达式键名（<code>var object = { \[expression\]: value }</code>）代替先定义对象（<code>var object = {}</code>）再赋值（<code>object\[expression\] = value</code>）的二步法。</p>
<p>最后，不能在 JavaScript 语句的位置上放一个光秃秃的对象表达式，因为
JavaScript 会把它解释成语句块，在 REPL 中输入 <code>{ a: 1 }</code> 相当于：</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span>
    <span class="nx">a</span><span class="o">:</span>
    <span class="mi">1</span>
<span class="p">}</span>
</code></pre></div>


<p>会得到1，输入 <code>{ a: 1, b: 2 }</code> 会得到语法错误。这种情况下，应该<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Using_object_initializers">用圆括号
<code>()</code>
把对象表达式括起来</a>。</p>
<h3>elm 调查</h3>
<ul>
<li>elm 有 vscode、intellij 和 sublime 的 IDE
    插件，见<a href="https://guide.elm-lang.org/install.html">文档</a>。</li>
<li>elm 可以通过 Task 实现异步调用，并且提供了类似 JavaScript Promise 的
    API，<a href="https://tech.allo-media.net/learning/elm/2018/02/05/chaining-http-requests-in-elm.html">例子</a></li>
<li>elm 没有提供服务端的正式支持</li>
<li>elm 中可以使用 elm-community/elm-test（<a href="https://github.com/elm-community/elm-test">elm 0.19+ 使用
    elm-explorations/test</a>）写单元测试，<a href="https://medium.com/@_rchaves_/testing-in-elm-93ad05ee1832">例子</a></li>
<li><a href="https://package.elm-lang.org/help/documentation-format">注释文档的格式</a></li>
<li>框架</li>
<li>控件</li>
<li>npm</li>
<li>webpack</li>
<li>css 封装</li>
</ul>
<p>elm 没有类似于 redux、vuex 的 store pattern，因为 elm
的核心不是组件，而是函数，<a href="https://www.reddit.com/r/elm/comments/5xdl9z/elm_architecture_with_a_reduxlike_store_pattern/dehrcx8?utm_source=share&amp;utm_medium=web2x">这篇讨论</a>引用了一段原来存在于
elm 文档中的话（现在已经不在了）：</p>
<blockquote>
<p>If you are coming from JavaScript, you are probably wondering “where
are
my reusable components?” and “how do I do parent-child communication
between them?” A great deal of time and effort is spent on these
questions in JavaScript, but it just works different in Elm. <strong>We do not
think in terms of reusable components.</strong> Instead, we focus on reusable
<em>functions</em>. It is a functional language after all!</p>
</blockquote>
<p>如果 elm
中的任何部分需要拆分，作者也<a href="https://www.reddit.com/r/elm/comments/5jd2xn/how_to_structure_elm_with_multiple_models/dbkpgbd/">给出了建议</a>。</p>
<p>elm 的不足：</p>
<ul>
<li>不稳定，elm 还没有到 1.0，每次发布新的版本都会引入不兼容的变化，例如从 0.18 到 0.19，标准库的名空间从 elm-lang 变成了 elm。</li>
<li>无法从 elm package 的 url 中看出这个 package 适用的 elm 版本。</li>
</ul>
<p><a href="https://www.zhihu.com/question/49028379/answer/118358205">批评</a>及其<a href="https://www.zhihu.com/question/49028379/answer/157178126">反驳</a></p>
<p>elm <a href="https://medium.com/@eeue56/top-6-ways-to-make-your-elm-app-crash-at-runtime-562b2fa92d70">并非没有 Runtime Error</a>，其实无所谓，个人认为，没有 Runtime Error，只能当理想，不能当真。</p>
<h3>npm 技巧</h3>
<p>以 @ 开头的包称为 scoped
packages，有两个好处，明确发布者和避免命名冲突。见<a href="https://stackoverflow.com/a/36667439/9634290">这篇回答</a>。</p>
<p>npm 安装的时候指定包的版本 \^1.0.0，\~1.2.1 等属于 <a href="https://docs.npmjs.com/about-semantic-versioning">npm semantic
versioning</a>，\^1.0.0
表示 major version 匹配，minor version &gt;= 0 的包，patch version
任意，而\~1.2.1 表示 major version 和 minor version 匹配，patch version
&gt;= 1 的包，<a href="https://semver.npmjs.com/">npm semver
计算器</a>提供了很形象的交互式界面。输入命令为
npm install &lt;package&gt;@&lt;semver&gt; 可选择安装特定版本的包。</p>
<p>npm list 可以查看本地安装的包的信息，包括版本和被依赖的路径：</p>
<div class="highlight"><pre><span></span><code>npm list &lt;package&gt;  <span class="c1"># 列出安装在项目中的 &lt;package&gt; 的信息</span>
npm list --depth<span class="o">=</span><span class="m">0</span>  <span class="c1"># 列出项目直接依赖的包</span>
npm list -g &lt;package&gt;  <span class="c1"># 列出全局安装的 &lt;package&gt; 的信息</span>
</code></pre></div>


<p>npm peerDependencies
一般用于插件指定宿主的版本，详见<a href="https://nodejs.org/en/blog/npm/peer-dependencies/">这篇文章</a>。</p>
<h3>data url 格式</h3>
<p>完整格式如下：</p>
<div class="highlight"><pre><span></span><code><span class="c">data:[&lt;mime type&gt;][;charset=&lt;charset&gt;][;base64],&lt;encoded data&gt;</span>
</code></pre></div>


<p>常用的前缀是：</p>
<div class="highlight"><pre><span></span><code><span class="n">data</span><span class="o">:</span><span class="n">image</span><span class="o">/</span><span class="n">gif</span><span class="o">;</span><span class="n">base64</span><span class="o">,</span>
</code></pre></div>


<h3>qpdf 从文件读取加密参数</h3>
<p>命令行中的加密参数没有秘密可言的，qpdf 提供了从文件读取加密参数的方式</p>
<div class="highlight"><pre><span></span><code><span class="err">qpdf @encrypt_params.txt test_input.pdf test_passwd.pdf</span>
</code></pre></div>


<p>但文件中的<a href="https://github.com/qpdf/qpdf/issues/16#issuecomment-352467583">加密参数必须换行</a>，每一行放置一个参数，如下所示。</p>
<div class="highlight"><pre><span></span><code><span class="err">--encrypt</span>
<span class="err">helloworld12345</span>
<span class="err">helloworld12345</span>
<span class="err">40</span>
<span class="err">--</span>
</code></pre></div>


<h3>阻止访问特定 IP</h3>
<p>Linux 中可以用 iptables 阻止对特定 IP
的访问，分为2种，一种是拦截发送往指定 IP 的包：</p>
<div class="highlight"><pre><span></span><code><span class="err">sudo iptables -A OUTPUT -d &lt;address&gt; -j DROP</span>
</code></pre></div>


<p>一种是拦截接收自指定 IP 的包：</p>
<div class="highlight"><pre><span></span><code><span class="err">sudo iptables -A INTPUT -s &lt;address&gt; -j DROP</span>
</code></pre></div>


<p>上述命令中将 -A（添加规则）改为 -D（删除规则）可以取消对特定 IP
的访问限制。其余参数 -d 表示目标站点，-s 表示来源站点，-j
表示对网络包采取的动作（DROP 表示丢弃网络包，即拦截访问），INPUT
表示流入，OUTPUT 表示流出。</p>
<h3>Apache2 mod_rewrite</h3>
<p>RewriteRule 和 RewriteCond 的匹配规则是，一条 RewriteRule
和它前面的一条或多条 RewriteCond 匹配，RewriteCond 不能匹配多条
RewriteRule。证据之一是<a href="https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html">文档</a>中
RewriteCond 部分的这段话：</p>
<p>The RewriteCond directive defines a rule condition. One or more
RewriteCond can precede a RewriteRule directive. The following rule is
then only used if both the current state of the URI matches its pattern,
and if these conditions are met.</p>
<p>以及 <a href="https://httpd.apache.org/docs/2.4/rewrite/intro.html">mod_rewrite
介绍</a>中对于正则表达式反向引用的说明。</p>
<p>最有力的证据是下面的例子：</p>
<div class="highlight"><pre><span></span><code><span class="n">RewriteCond</span><span class="w">  </span><span class="ss">&quot;%{HTTP_USER_AGENT}&quot;</span><span class="w">  </span><span class="ss">&quot;(iPhone|Blackberry|Android)&quot;</span><span class="w"></span>
<span class="n">RewriteRule</span><span class="w">  </span><span class="ss">&quot;^/$&quot;</span><span class="w">                 </span><span class="ss">&quot;/homepage.mobile.html&quot;</span><span class="w">  </span><span class="o">[</span><span class="n">L</span><span class="o">]</span><span class="w"></span>

<span class="n">RewriteRule</span><span class="w">  </span><span class="ss">&quot;^/$&quot;</span><span class="w">                 </span><span class="ss">&quot;/homepage.std.html&quot;</span><span class="w">     </span><span class="o">[</span><span class="n">L</span><span class="o">]</span><span class="w"></span>
</code></pre></div>


<p>只有 RewriteCond 只能匹配一条 RewriteRule 的情况下才能解释得通。</p>
<p>由于 RewriteRule 是 mod_rewrite
中真正干活的部分，可以把它看作语句，RewriteCond
则是语句的修饰，而非控制流。一个 RewriteEngine 就是一条条 RewriteRule
不停执行的过程，直到执行完毕或者某一条 RewriteRule 触发了
END，终止这个过程。</p>
<h3>socket 文件不可放在 /tmp 目录中</h3>
<p>用于进程间通信的 unix socket 文件不可以放在 /tmp 目录中，至少在 Fedora
和 CentOS 中，进程只能看到自己创建的
socket，其余进程访问的时候就找不到文件。详见<a href="https://serverfault.com/a/464025">这篇回答</a>和<a href="https://stackoverflow.com/a/22277617/9634290">这篇回答</a>。</p>
<h3>开始一个 Quasar 项目</h3>
<p><a href="https://quasar-framework.org/guide/quasar-cli.html">开始一个 Quasar
项目</a>非常简单，假设
node 已经安装，只要运行两个命令，按照提示操作就可以了。</p>
<div class="highlight"><pre><span></span><code><span class="err">npm install -g quasar-cli</span>
<span class="err">quasar init &lt;folder-name&gt;</span>
</code></pre></div>


<p>在 Android 环境已经准备好的情况下，<a href="https://quasar-framework.org/guide/cordova-preparation.html">添加 Android
编译功能</a>也不难：</p>
<div class="highlight"><pre><span></span><code><span class="err">npm install -g cordova</span>
<span class="err">quasar mode -a cordova</span>
<span class="err">cd src-cordova</span>
<span class="err">cordova platform add android</span>
</code></pre></div>


<h3>Vue 调试插件因为没有签名被禁用</h3>
<p>最近的火狐浏览器禁用了没有签名的浏览器扩展，包括 Vue 调试插件，在 Vue
发布签名版的调试插件之前，只能在火狐浏览器中输入 about:config，搜索
xpinstall.signatures.required 选项，并且将其设置为 false，才可以使用 Vue
调试插件。</p>
<h3>使用 ImageMagick 转化 SVG 注意事项</h3>
<p>生成的图像和浏览器中看到的不一致，安装 Inkscape
解决了这个问题，原因不明。</p>
<h3>用户登录时启动 VirtualBox 虚拟机</h3>
<p><a href="https://superuser.com/a/1027414">把虚拟机快捷方式添加到启动文件夹</a>。</p>
<h3>在 VirtualBox 虚拟机中配置 Samba 服务</h3>
<p>首先要为虚拟机添加一块 Host-Only 连接的网络适配器，默认的网络连接是
NAT，主机中看不到（参考<a href="https://askubuntu.com/a/634076">这条回答</a>）。</p>
<p>在虚拟机中<a href="https://tutorials.ubuntu.com/tutorial/install-and-configure-samba#1">安装 samba server
软件</a>（虚拟机操作系统为
Ubuntu 18.04 amd64）：</p>
<div class="highlight"><pre><span></span><code><span class="err">sudo apt update</span>
<span class="err">sudo apt install samba</span>
</code></pre></div>


<p>在 /etc/samba/smb.conf
配置文件末尾<a href="https://tutorials.ubuntu.com/tutorial/install-and-configure-samba#2">添加如下共享目录设置</a>：</p>
<div class="highlight"><pre><span></span><code><span class="k">[sambashare]</span>
    <span class="na">comment</span> <span class="o">=</span> <span class="s">Samba on Ubuntu</span>
<span class="s">    path = /home/&lt;username&gt;/sambashare</span>
<span class="s">    read only = no</span>
<span class="s">    browsable = yes</span>
</code></pre></div>


<p>重启 samba 服务，应用配置：</p>
<div class="highlight"><pre><span></span><code><span class="err">systemctl restart smbd</span>
</code></pre></div>


<p>为 samba
共享服务<a href="https://tutorials.ubuntu.com/tutorial/install-and-configure-samba#3">添加用户</a>：</p>
<div class="highlight"><pre><span></span><code><span class="err">sudo smbpasswd -a &lt;username&gt;</span>
</code></pre></div>


<p>此处的 username
应该为系统中存在的用户，因为前面把共享目录放在了某位用户的主目录中，这里也应该把同一位用户添加到
samba
用户中，以免折腾文件和目录的权限设置，该命令运行完毕就可以通过该用户名和新添加的密码来访问共享目录了。</p>
<h3>urljoin 使用技巧</h3>
<p>要在 base url 后面添加路径内容，确保 base url 以“/”结尾。</p>
<div class="highlight"><pre><span></span><code><span class="err">&gt;&gt;&gt; urljoin(&#39;http://a.b.c/d&#39;, &#39;e&#39;)</span>
<span class="err">&#39;http://a.b.c/e&#39;</span>
<span class="err">&gt;&gt;&gt; urljoin(&#39;http://a.b.c/d/&#39;, &#39;e&#39;)</span>
<span class="err">&#39;http://a.b.c/d/e&#39;</span>
</code></pre></div>


<h3>Selenium 使用小结（Python）</h3>
<p>连接 Firefox 浏览器：</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">selenium</span> <span class="kn">import</span> <span class="n">webdriver</span>
<span class="n">driver</span> <span class="o">=</span> <span class="n">webdriver</span><span class="o">.</span><span class="n">Firefox</span><span class="p">()</span>
</code></pre></div>


<p>浏览页面：</p>
<div class="highlight"><pre><span></span><code><span class="err">driver.get(&quot;http://www.python.org&quot;)</span>
</code></pre></div>


<p>查找单个元素（使用 find_element_by_*）：</p>
<div class="highlight"><pre><span></span><code><span class="err">toc = driver.find_element_by_class_name(&#39;toc-B9k&#39;)</span>
</code></pre></div>


<p>查找子元素：</p>
<div class="highlight"><pre><span></span><code><span class="err">link_root = toc.find_element_by_class_name(&#39;tree-view_container&#39;)</span>
</code></pre></div>


<p>查找多个元素（使用 find_elements_by_*）：</p>
<div class="highlight"><pre><span></span><code><span class="err">parts = driver.find_elements_by_css_selector(&#39;[data-text=&quot;true&quot;]&#39;)</span>
</code></pre></div>


<p>选择 Firefox profile（指定 firefox_profile 选项为 profile
目录的绝对路径或者为一个 FirefoxProfile 对象）：</p>
<div class="highlight"><pre><span></span><code><span class="n">fp</span> <span class="o">=</span> <span class="n">webdriver</span><span class="p">.</span><span class="n">FirefoxProfile</span><span class="p">(</span><span class="s1">&#39;/Users/&lt;username&gt;/Library/Application Support/Firefox/Profiles/71v1uczn.default&#39;</span><span class="p">)</span>

<span class="n">driver</span> <span class="o">=</span> <span class="n">webdriver</span><span class="p">.</span><span class="n">Firefox</span><span class="p">(</span><span class="n">fp</span><span class="p">)</span>

<span class="o">#</span> <span class="err">或者</span>
<span class="o">#</span> <span class="n">driver</span> <span class="o">=</span> <span class="n">webdriver</span><span class="p">(</span><span class="s1">&#39;/Users/&lt;username&gt;/Library/Application Support/Firefox/Profiles/71v1uczn.default&#39;</span><span class="p">)</span>
</code></pre></div>


<p>获取元素的属性值：</p>
<div class="highlight"><pre><span></span><code><span class="err">href = link.get_attribute(&#39;href&#39;)</span>
</code></pre></div>


<p>获取元素的内部文本（仍使用 get_attribute）：</p>
<div class="highlight"><pre><span></span><code><span class="err">title = link.get_attribute(&#39;innerText&#39;)</span>
</code></pre></div>


<h3>Selenium 注意事项</h3>
<p>Ubuntu 中需要下载安装
<a href="https://github.com/mozilla/geckodriver/releases">geckodriver</a>
才可以使用 webdriver.Firefox()，仅安装
<a href="https://packages.ubuntu.com/bionic/firefoxdriver">firefoxdriver</a>
安装包（不确定其用途是什么）是不够的。</p>
<p>Selenium <a href="https://stackoverflow.com/a/10726369/9634290">无法准确判断</a>
JavaScript 脚本的执行情况，如果要等待 JavaScript 执行完成，可以调用
sleep()，或者<a href="https://stackoverflow.com/a/12859689/9634290">使用 Selenium explicit
wait</a>（<a href="https://stackoverflow.com/a/28067495/9634290">不推荐使用
implicit wait</a>）。</p>
<h3>pandoc 无法把 pdf 文件转化成其它格式</h3>
<p>根本原因在于 pdf
是一种布局格式，而非结构化的文档格式。参考<a href="https://groups.google.com/d/msg/pandoc-discuss/qN71bny2L-Y/iuCg74p3SNUJ">这个回答</a>。</p>
<h3>冷盘冷电和热盘热电</h3>
<p>冷盘冷电指服务器更换硬盘设备需要关机重启，热盘热电指服务器可以在运行的状态更换硬盘设备。</p>
<h3>LOM 网卡技术</h3>
<p>LOM 全称 LAN on Motherboard，由 Intel
推出，直接在主板上集成网卡，无需占用一个插槽安装网卡，兼容传统以太网的
10M/100M 接口，也可以直接升级到 1000M 的高速网络接口，支持
Windows、Linux 等众多操作系统。</p>
<h3>禁用 q-input 用户输入</h3>
<p>使用 disable 属性而非 disabled，后者可以改变外观，但用户仍可输入内容。</p>
<h3>Seahorse 无法导入 openssh 密钥</h3>
<p>表现是点击导入密钥，可以打开，输入
passphrase（如果有）后会显示密钥信息，但导入按钮显示为禁用。临时解决方法是把密钥的私钥和公钥复制到
.ssh 目录下，seahorse 就会自动导入该密钥。</p>
<h3>DRF 自动表单验证</h3>
<p>写入 DRF 的 SlugRelatedField
的值会被自动转化成目标模型的一个实例，如果找不到与写入值对应的实例，DRF
会返回 400 错误，无需写 validator 来实现这一功能。</p>
<p>如果一个 Field 指定了 many=True，DRF
只会接受数组，如果不是数组，便会返回 400 错误。</p>
<h3>规避 Adobe Flash Player 大陆版</h3>
<p>升级了 Adobe Flash Player
后，第二天就收到一条国内网民司空见惯的骚扰弹窗，查了一下，原来是 Adobe
Flash Player
<a href="http://www.staycu.com/archives/486">在中国大陆发行后</a>，已经<a href="https://lgwangh.github.io/2018/10/03/%E5%A6%82%E4%BD%95%E6%91%86%E8%84%B1Adobe-Flash-player%E7%9A%84%E6%B5%81%E6%B0%93%E9%AA%9A%E6%89%B0/">变成了流氓软件</a>，这年头基本上用不到
Adobe Flash Player，卸载，需要时再想办法安装国际版的，以免中招。</p>
<h3>Django CharField max_length 的含义</h3>
<p>max_length 指的是字符数，不是字节数，例如在
<a href="https://dev.mysql.com/doc/refman/8.0/en/char.html">MySQL</a>中，CharField(max_length=100)
被转化成 varchar(100)，即100个字节的字符串。max_length 的值<a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#charfield">被 Django
用来验证字符串的长度</a>，同时也会影响数据库中可实际存储字符串的长度。</p>
<h3>Django Field 属性</h3>
<p>以下是 Django Field 的常用属性：</p>
<ul>
<li>null=True，可以用 null 表示空值</li>
<li>blank=True，允许空值</li>
<li>choices，有效值的集合，用于验证和表单项的显示</li>
<li>db_column，数据库中字段的名称</li>
<li>db_index，为数据库字段创建索引</li>
<li>default，默认值</li>
<li>editable，可否修改</li>
<li>error_messages，错误提示</li>
<li>help_text，帮助文本</li>
<li>unique=True，不允许重复值</li>
<li>unique_(year|month|date)=True，不允许相同的年/月/日期的出现</li>
<li>primary_key，主键，隐含 blank=False，db_index=True，unique=True</li>
<li>validators，用于字段验证</li>
<li>verbose_name，方便阅读的名字</li>
</ul>
<p>下面的流程会对如何使用这些属性有所帮助。</p>
<p>创建 Django Model 时应该考虑是否需要自定义主键，如果需要，创建 Field
并指定 primary_key=True。</p>
<p>如若不需要，Model 会自己创建一个：</p>
<div class="highlight"><pre><span></span><code><span class="err">id = models.AutoField(primary_key=True)</span>
</code></pre></div>


<p>创建 Django Model Field 的时候，首先设置
verbose_name，起到注释的作用，接下来提一些问题：</p>
<ul>
<li>不允许重复值？unique=True</li>
<li>需要指定默认值？设置 default 属性</li>
<li>允许空值？blank=True</li>
<li>空值允许为 null？null=True（尽量不要为 CharField
    指定该选项，因为字符串空值应该为空字符串）</li>
<li>是否需要搜索该字符串？可能的话，设置db_index=True</li>
</ul>
<p>如果这个 Field 指向其它 Model，需要考虑本模型和目标模型的对应关系：</p>
<ul>
<li>多对一（本模型的多个实例和目标模型的一个实例有对应关系，反之不成立），使用
    ForeignKey</li>
<li>多对多（本模型的多个实例和目标模型的一个实例有对应关系，反之亦然），使用
    ManyToManyField</li>
<li>一对一（本模型的实例和目标模型的实例一一对应），使用 OneToOneField</li>
</ul>
<p>对于 ForeignKey 和 OneToOneField，需要设置 on_delete 属性，一般设置为
on_delete=models.CASCADE，明确本模型从属于目标模型，如果目标实例被删除，也删除本实例。</p>
<p>还需要考虑反向引用的问题，反向引用指的是，根据已定义的对应关系，在目标模型中自动创建到本模型的引用。如果<strong>不需要</strong>在目标模型中创建反向引用，可以明确设置
related_name='+'，否则 related_name
的设置取决于本模型是否是抽象模型，如果是抽象模型，最好设置为
'%(app_label)s_%(class)s_xxx'，避免不同子模型反向引用的命名冲突，如果不是抽象模型，直接设置成想要的名字就可以了。</p>
<h3>Django 禁用 admin 站点的思路</h3>
<ul>
<li>限定用户使用，通过设置用户的 is_staff 属性</li>
<li>删除 admin 站点的 URL 配置</li>
<li>从 INSTALLED_APPS 里删除 django.contrib.auth 应用</li>
</ul>
<p>从上往下越来越彻底。注：只是思路，没有实验验证，也没有上网验证。</p>
<h3>Vue Template 中慎用表达式</h3>
<p>在不该出现表达式的地方使用了表达式会导致模板编译错误，如果是在运行的时候编译模板（quasar
dev 环境），则表现为调试 console 迟迟不能准备好，陷入死循环，但 CPU
占用未达到 100%。</p>
<h3>HTML 中插入 JavaScript 的建议</h3>
<ul>
<li>使用外部脚本</li>
<li>不要使用 async 或者 defer</li>
</ul>
<p>原因是浏览器支持不好，在《JavaScript
高级编程》中详细讨论了使用内嵌脚本可能引起的 HTML 解析问题，以及相应的
hack。明确指出不是所有浏览器都支持 defer 的顺序加载，async
则明确了不按顺序加载。</p>
<h3>修改 wsgi 站点代码</h3>
<p>如果想让修改立即反映到站点，<a href="https://modwsgi.readthedocs.io/en/develop/user-guides/reloading-source-code.html">最靠谱的方法是重启
apache</a>。</p>
<h3>DRF 创建额外的 _url 链接</h3>
<p>如果使用 HyperlinkedModelSerializer，所有指向其它模型的字段都显示为
url，如果要把这些显示为链接的字段名改为以 _url 结尾，可以自定义
HyperlinkedRelatedField 的 source 参数：</p>
<div class="highlight"><pre><span></span><code><span class="err">user_url = serializers.HyperlinkedRelatedField(view_name=&#39;user-detail&#39;, read_only=True, source=&#39;user&#39;)</span>
</code></pre></div>


<p>如果这个字段对应着多个外部模型的实例，不想显示多个链接，只显示一个通过
query string 进行筛选的链接，则需要使用 SerializerMethodField
自定义链接字段：</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="n">TeamSerializer</span>(<span class="n">HyperlinkedModelSerializer</span>):
    ...
    <span class="n">members_url</span> = <span class="n">serializers</span>.<span class="n">SerializerMethodField</span>()

    <span class="n">def</span> <span class="n">get_members_url</span>(<span class="k">self</span>, <span class="n">obj</span>):
        <span class="k">return</span> <span class="n">get_query_url</span>(
            <span class="n">view_name</span>=<span class="s">&#39;staff-list&#39;</span>,
            <span class="n">request</span>=<span class="k">self</span>.<span class="nb">context</span>[<span class="s">&#39;request&#39;</span>],
            <span class="n">query_params</span>={
                <span class="s">&#39;team&#39;</span>: <span class="n">obj</span>.<span class="n">id</span>,
            },
        )

<span class="n">def</span> <span class="n">get_query_url</span>(<span class="n">view_name</span>, <span class="n">request</span>, <span class="n">query_params</span>):
    <span class="k">return</span> <span class="s">&#39;{url}?{qs}&#39;</span>.<span class="n">format</span>(
        <span class="n">url</span>=<span class="nb">reverse</span>(<span class="n">viewname</span>=<span class="n">view_name</span>, <span class="n">request</span>=<span class="n">request</span>),
        <span class="s">qs=urlencode(query_params),</span>
<span class="s">    )</span>
</code></pre></div>


<h3>调试 Cordova 应用的 JavaScript 错误</h3>
<p>Chrome 远程调试需要 debug 版本的应用，目前在 AndroidManifest.xml 的
application 标签中加上 android:debuggable="true" 没有用，而且还会触发
HardcodedDebugMode 错误，Cordova
会提示这么写容易不小心在发布的应用中混入程序的调试信息。正确的做法是在编译命令中加入
--debug 选项，quasar build -m cordova -T android --debug 会调用 cordova
build --debug android，这样编译出来的应用的 WebView 窗口才可以被 Chrome
看到。但是 Chrome 远程调试的效果不理想，在 adb logcat 中输出的
JavaScript 错误信息并不能在远程调试的 console 中看到。</p>
<p>好在 debug 版本的应用的 JavaScript
没有经过最小化和压缩处理，非常容易定位到出错的位置，现在确定是 vendor.js
中的箭头函数引发了语法错误（Android 4.4 的 WebView 还是 Chromium
30）。现在在尝试让 babel 把 vendor.js 中的代码转译一下。</p>
<p>后来安装了 targets_webpack_plugin，并在 webpack 配置的 plugins
列表中添加</p>
<div class="highlight"><pre><span></span><code><span class="err">new TargetsPlugin({</span>
<span class="err">    browsers: [&#39;last 2 versions&#39;, &#39;chrome &gt;= 30&#39;]</span>
<span class="err">})</span>
</code></pre></div>


<p>但又遇到 Proxy not defined 的问题，到现在为止遇到的问题都是 Luxon
引起的。</p>
<h3>JavaScript Promise</h3>
<p>Promise 最重要的属性是 then，把它做对了，Promise
也就做对了。<a href="https://gist.github.com/domenic/3889970">这篇文章</a>指出了
Promise 的目的，以及 Promise 实现的问题。<a href="https://promisesaplus.com/">Promise
A+</a> 则给出了关于什么是 Promise 的严格标准。</p>
<p>应该使用哪一种
Promise，<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">ES6</a>，<a href="https://github.com/jakearchibald/es6-promise">es6-promise</a>、<a href="https://github.com/tildeio/rsvp.js">rsvp</a>、<a href="https://github.com/kriskowal/q">q</a>、Bluebird？</p>
<p>应该了解使用 Promise 的
Pattern，见<a href="https://medium.com/datafire-io/es6-promises-patterns-and-anti-patterns-bbb21a5d0918">这篇文章</a>和
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises">MDN
讲解</a>。</p>
<h3>Vue 组件的插槽</h3>
<p>如果想在组件中插入一段 HTML 代码：</p>
<div class="highlight"><pre><span></span><code><span class="nt">&lt;component&gt;</span>
  <span class="nt">&lt;div&gt;</span>More html content<span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/component&gt;</span>
</code></pre></div>


<p>可以使用<a href="https://vuejs.org/v2/guide/components-slots.html">插槽</a>来实现，在组件的
HTML 模板中添加一对 &lt;slot&gt; 标签（可以在其中添加默认内容）：</p>
<div class="highlight"><pre><span></span><code><span class="nt">&lt;template&gt;</span>
  <span class="nt">&lt;slot&gt;</span>Default content<span class="nt">&lt;/slot&gt;</span>
<span class="nt">&lt;/template&gt;</span>
</code></pre></div>


<p>如果 &lt;component&gt; 和 &lt;/component&gt; 之间有内容，则 &lt;slot&gt;
标签之间的部分被该内容替换，否则显示默认内容。</p>
<h3>Android WebView 兼容性</h3>
<p>从 Android 4.4 KitKat（sdk version 19）开始，使用 chromium 作为其默认的
webview，KitKat 将使用 chromium
30，并且不会升级。见<a href="http://www.mobilexweb.com/blog/android-4-4-kitkat-browser-chrome-webview">这篇报道</a>。</p>
<h3>Django INSTALLED_APPS</h3>
<p>在 Django 中使用一个应用 myapp，需要把它放在 settings 模块的
INSTALLED_APPS 列表中，这里可以直接写应用的名字：</p>
<div class="highlight"><pre><span></span><code><span class="err">INSTALLED_APPS = [</span>
<span class="err">    ...</span>
<span class="err">    &#39;myapp&#39;,</span>
<span class="err">    ...</span>
<span class="err">]</span>
</code></pre></div>


<p>也可以写应用的一个配置类（django.apps.AppConfig 的子类）:</p>
<div class="highlight"><pre><span></span><code><span class="err">INSTALLED_APPS = [</span>
<span class="err">    ...</span>
<span class="err">    &#39;myapp.apps.NiceConfig&#39;,</span>
<span class="err">    ...</span>
<span class="err">]</span>
</code></pre></div>


<p>对于第一种情况，如果 myapp 的 __init__.py 中定义了
default_app_config：</p>
<div class="highlight"><pre><span></span><code><span class="err">default_app_config = PrettyConfig</span>
</code></pre></div>


<p>则使用 default_app_config 指定的配置类 PrettyConfig。否则使用
AppConfig。这是旧版的写法，这种情况下应用的配置是固定的。</p>
<p>Django 1.7 以来就推荐第二种写法，用户可以根据其需求自由地选择
PrettyConfig 或者 NiceConfig，</p>
<h3>筛选 adb logcat 的输出</h3>
<p>如果不写筛选参数，adb logcat
会输出所有内容，写了筛选参数，按照筛选参数进行筛选后输出。</p>
<p>adb logcat 的输出可以有许多不同的频道，每个频道都用一个 tag
表示，写到筛选参数中的
tag，会按照选项指定的方式进行筛选，没有出现在筛选参数中的 tag
会输出其全部内容。</p>
<p>首先加上 *:S 筛选参数，* 匹配所有的 tag，S
表示没有输出，合起来就是默认不显示任何 tag 的输出。然后只对关心的 tag
写参数，如 chromium:I 表示只显示 chromium tag 的 I
<a href="https://developer.android.com/studio/command-line/logcat.html#filteringOutput">级别</a>以上的输出，完整的命令就是
adb logcat chromium:I *:S。</p>
<h3>django-cors-headers 设置的注意事项</h3>
<p>CORS_ORIGIN_WHITELIST 和 CORS_ORIGIN_REGEX_WHITELIST
有明显的区别，前者匹配 源站的域名和端口号（不包括 http 或 https
部分），如果要匹配 http://127.0.0.1:8080，必须写成
127.0.0.1:8080，不存在只匹配 http://127.0.0.1:8080 但不匹配
https://127.0.0.1:8080 的情况，后者匹配源站的源（包括 http 或 https
部分），如果要匹配 http://127.0.0.1:8080，则至少有一个正则表达式匹配
http://127.0.0.1:8080 的全部。</p>
<p>判断源是否在白名单中的代码如下：</p>
<div class="highlight"><pre><span></span><code>    <span class="k">def</span> <span class="nf">origin_found_in_white_lists</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">origin</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
        <span class="k">return</span> <span class="p">(</span>
            <span class="n">url</span><span class="o">.</span><span class="n">netloc</span> <span class="ow">in</span> <span class="n">conf</span><span class="o">.</span><span class="n">CORS_ORIGIN_WHITELIST</span>
            <span class="ow">or</span> <span class="p">(</span><span class="n">origin</span> <span class="o">==</span> <span class="s1">&#39;null&#39;</span> <span class="ow">and</span> <span class="n">origin</span> <span class="ow">in</span> <span class="n">conf</span><span class="o">.</span><span class="n">CORS_ORIGIN_WHITELIST</span><span class="p">)</span>
            <span class="ow">or</span> <span class="bp">self</span><span class="o">.</span><span class="n">regex_domain_match</span><span class="p">(</span><span class="n">origin</span><span class="p">)</span>
        <span class="p">)</span>
</code></pre></div>


<h3>Django 部署</h3>
<ul>
<li><a href="https://stackoverflow.com/a/18966632/9634290">pip freeze</a></li>
<li><a href="https://gist.github.com/GrahamDumpleton/b380652b768e81a7f60c">为 Django WSGI
    进程设置环境变量</a></li>
<li>使用 MySQL 数据库需要安装 mysqlclient（pip），意味着需要安装
    python3-dev、gcc 和 default-libmysqlclient-dev（apt）</li>
</ul>
<h3>有关 CSRF 的几个链接收藏</h3>
<ul>
<li><a href="https://www.django-rest-framework.org/topics/ajax-csrf-cors/">DRF 关于 CSRF
    的建议</a></li>
<li><a href="https://docs.djangoproject.com/en/2.2/ref/csrf/">Django 关于 CSRF
    防范的建议</a></li>
<li><a href="https://blog.codinghorror.com/preventing-csrf-and-xsrf-attacks/">Jeff Atwood 关于 CSRF
    的博文</a></li>
</ul>
<h3>在代码的一些区域关闭质量检查的方法</h3>
<p>pylint：参照<a href="https://stackoverflow.com/a/23542817/9634290">这篇回答</a>，在代码块中单独插入一行注释（关闭代码块中该行之后的代码检查）或者在代码行后添加注释（关闭该行的代码检查），注释内容如下：</p>
<div class="highlight"><pre><span></span><code><span class="err"># pylint: disable=no-member, line-too-long</span>
</code></pre></div>


<p>eslint：把需要关闭检查的代码区域用两条注释包起来：</p>
<div class="highlight"><pre><span></span><code><span class="err">/* eslint-disable no-self-compare */</span>
<span class="err">const isNaN = v =&gt; v !== v</span>
<span class="err">/* eslint-enable no-self-compare */</span>
</code></pre></div>


<h3>Django 字符集设置</h3>
<p>Django 完全支持 unicode 文本，因此，在 Django 程序内部使用 Python3 str
类型传递文本<a href="https://docs.djangoproject.com/en/2.1/ref/unicode/#unicode-data">总是没有错的</a>。任何地方遇到非
unicode 文本（bytestring），第一时间把它们转化成 unicode
文本（str），如果应用不这么做，Django 就会代劳，但它会假定该 bytestring
是 utf-8 编码。</p>
<p>至于 Django 应用和数据库的接口，只要<a href="https://docs.djangoproject.com/en/2.1/ref/unicode/#creating-the-database">确保创建的数据库可以存储 unicode
文本</a>。</p>
<p>如果需要向客户端返回非 utf-8 编码的文本，应该修改 <a href="https://docs.djangoproject.com/en/2.1/ref/settings/#std:setting-DEFAULT_CHARSET">DEFAULT_CHARSET
设置</a>。</p>
<h3>URL、URI 和 URN</h3>
<p>简单地说，URL 包含了如何定位这个资源的全部信息，URN 不包含此类信息。URL
和 URN 都是 URI。</p>
<p>URI 和通常意义上的资源的对应关系比较松散：</p>
<ul>
<li>资源不见得唯一不变，http://example.org/1.jpg
    可能今天是一张猫的照片，明天变成了一张狗的照片。</li>
<li>同资源可能对应多个 URI，如 http://example.org/1.jpg 和
    ftp://example.com/pub/1.jpg 可能对应同一张图片。</li>
<li><a href="https://danielmiessler.com/study/url-uri/">有人</a>说 index.html 是
    URN，可这几乎没有告诉我任何关于这个资源的任何信息。我怀疑是作者理解错了，因为它根本不符合
    <a href="https://en.wikipedia.org/wiki/Uniform_Resource_Name">URN 的语法</a>。</li>
<li>也有定义比较精确的 URI，如
    <a href="https://en.wikipedia.org/wiki/International_Standard_Book_Number">ISBN</a>。</li>
</ul>
<p>URL、URN 和 URI
的全部作用只是定义了一套语法，规定了要按照什么方式来书写资源的标识符，至于语义，则由具体的实现（http、mailto、isbn）来负责。一种
URI 对用户的帮助有多大，完全取决于实现的优劣。</p>
<h3>跨域资源共享（CORS）</h3>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Browser_compatibility">浏览器支持</a>：IE10
以上的所有主流浏览器，关键是服务器支持，判断的依据是服务器受到跨域请求的时候，在返回的消息头中包含
Access-Control-Allow-Origin 字段。</p>
<p>浏览器对<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simple_requests">简单请求</a>和非简单请求的处理是不一样的，对于简单请求，浏览器会在请求的消息头中包含一个
Origin 字段，标明发送请求的站点。对于复杂请求，除了在正式请求中包含
Origin 字段之外。还会发送一次 OPTIONS
预检请求（preflight），目的是询问服务器它即将发送的请求是否被允许。</p>
<p>以上服务器和浏览器对跨域共享的处理是自动完成的，对用户透明。</p>
<p>如果浏览器不支持 CORS，可以使用 <a href="https://mp.weixin.qq.com/s/HxJI0q_5_nqf-tKX2pz1FQ">JSONP
等其它方式</a>。</p>
<p>建议看<a href="http://www.ruanyifeng.com/blog/2016/04/cors.html">阮一峰的博客文章</a>，把原理讲得很清楚。以及<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">这篇
MDN 文章</a>。</p>
<p>跨域共享时，发送请求的利用客户浏览器非法获取第三方站点上的资源，甚至攻击第三方站点，而第三方站点也可能通过返回恶意的内容来攻击发送请求的站点。跨域请求如果处理不好，会引发许多安全问题，具体见<a href="https://code.google.com/archive/p/html5security/wikis/CrossOriginRequestSecurity.wiki">这篇文章</a>。</p>
<p>最后略提一下 Cordova，由于 Cordova
<a href="https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-whitelist/#network-request-whitelist">默认打开</a>了
&lt;access origin="*" /&gt;，可以向任何域发送请求，在服务端看 Cordova
应用发送过来的请求的消息头中也没有 Origin 字段。因此默认不需要为 Cordova
设置 CORS。</p>
<h3>JavaScript 札记</h3>
<p><a href="https://coderwall.com/p/5tlhmw/converting-strings-to-number-in-javascript-pitfalls">转化成 number 或者 int32</a></p>
<p><a href="https://stackoverflow.com/a/10382137/9634290"><code>o.length &gt;&gt;&gt; 0</code> 的含义</a></p>
<div class="highlight"><pre><span></span><code><span class="nx">x</span> <span class="o">-</span> <span class="mi">0</span> <span class="c1">// 把 x 转换成 number 类型</span>
<span class="o">~~</span><span class="nx">x</span>  <span class="c1">// 把 x 转换成32位整数</span>
<span class="nx">x</span> <span class="o">&gt;&gt;&gt;</span> <span class="mi">0</span>  <span class="c1">// 把 x 转换成31位非负整数</span>
</code></pre></div>


<p>转换成 boolean 类型</p>
<div class="highlight"><pre><span></span><code><span class="o">!!</span><span class="nx">x</span>  <span class="c1">// 等价于 Boolean(x)</span>
</code></pre></div>


<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean#Description">MDN
文档</a>有相关代码，看到许多举例
!!"false"反对的，不明白为什么这么多人希望一个长度不为0的字符串是
false。JavaScript
中有6个值是假值：false、0、NaN、空字符串（""）、undefined、null，其余均为真值，包括空数组（[]）、空对象（{}）、new
Boolean(false) 等所有对象。</p>
<div class="highlight"><pre><span></span><code><span class="nb">Number</span><span class="p">(</span><span class="kc">null</span><span class="p">)</span>  <span class="c1">// 0</span>
<span class="nb">Number</span><span class="p">(</span><span class="kc">undefined</span><span class="p">)</span>  <span class="c1">// NaN</span>
</code></pre></div>


<p>以上运算规则见 <a href="http://es5.github.io/#x9.3">JavaScript ToNumber</a></p>
<div class="highlight"><pre><span></span><code><span class="o">~~</span><span class="kc">undefined</span>  <span class="c1">// 0</span>
<span class="kc">undefined</span> <span class="o">&gt;&gt;&gt;</span> <span class="mi">0</span>  <span class="c1">// 0</span>
<span class="kc">undefined</span> <span class="o">|</span> <span class="mi">0</span>
</code></pre></div>


<p>因为进行<a href="http://es5.github.io/#x11.10">位运算</a>之前，先把 <code>undefined</code> 转化成 number，结果是 <code>NaN</code></p>
<div class="highlight"><pre><span></span><code><span class="o">~~</span><span class="kc">NaN</span>  <span class="c1">// 0</span>
<span class="kc">NaN</span> <span class="o">&gt;&gt;&gt;</span> <span class="mi">0</span>  <span class="c1">// 0</span>
<span class="kc">NaN</span> <span class="o">|</span> <span class="mi">0</span>
</code></pre></div>


<p>然后把 <code>NaN</code> 转化成32位整数，规则见 <a href="http://es5.github.io/#x9.5">JavaScript ToInt32</a>，结果是 <code>+0</code></p>
<p><code>Number.isNaN(x)</code> 表示 <code>x</code> 的值是 <code>NaN</code>，<code>isNaN(x)</code> 表示 <code>x</code> 不能被转化成合法的 <code>number</code> 类型。</p>
<h3>String.prototype.replace</h3>
<p>oldString.replace(pattern, string) 将 oldString 中和 pattern
匹配的部分替换成 string，返回替换后的结果，不改变 oldString
的内容（JavaScript string is immutable）。</p>
<h3>Webpack alias</h3>
<p>Webpack alias 会影响绝对路径导入的代码的位置，按照 alias
的源和目标的不同可分为多种情况，具体见 <a href="https://webpack.js.org/configuration/resolve/#resolvealias">Webpack
文档</a>提供的一张示例表。</p>
<p>简单的说，如果 alias 的源以 \<span class="math">\( 结尾，如 xyz\\)</span>，只改变 import 'xyz'
的含义，不改变 import 'xyz/some/module' 等语句的含义。如果 import
语句的含义没有被改变，则从 node_modules 目录下的相应模块中导入，如果
import 语句的含义被改变，则从 alias
目标所在的代码文件、或模块目录中导入。</p>
<h3>Visual Studio Code alias 配置</h3>
<p>配置 compilerOptions.paths：</p>
<div class="highlight"><pre><span></span><code><span class="err">  &quot;compilerOptions&quot;: {</span>
<span class="err">    &quot;baseUrl&quot;: &quot;.&quot;,</span>
<span class="err">    &quot;paths&quot;: {</span>
<span class="err">      &quot;components/*&quot;: [</span>
<span class="err">        &quot;src/components/*&quot;</span>
<span class="err">      ],</span>
<span class="err">      &quot;@/*&quot;: [</span>
<span class="err">        &quot;src/*&quot;</span>
<span class="err">      ]</span>
<span class="err">    }</span>
<span class="err">  }</span>
</code></pre></div>


<p>因为 paths 的是值相对于 baseUrl 的路径，因此 node_modules
中的模块路径中也要加上 node_modules</p>
<div class="highlight"><pre><span></span><code><span class="err">&quot;_&quot;: [ &quot;node_modules/lodash&quot; ]</span>
</code></pre></div>


<p>Jest alias
的配置参考<a href="https://medium.com/@justintulk/solve-module-import-aliasing-for-webpack-jest-and-vscode-74007ce4adc9">这篇文章</a>。</p>
<h3>Quasar 自动生成的代码的位置</h3>
<p>Quasar 自动生成的代码位于项目根目录的 .quasar 文件夹中。</p>
<h3>查看 Quasar 使用的 webpack 配置</h3>
<p>quasar build 和 quasar dev 不提供输出类似配置信息的选项，用户可以在扩展
webpack 配置的钩子函数中输出配置信息。</p>
<h3>Vue 发布的模块</h3>
<p>在 Vue 的 dist 目录中可以看到许多发布文件，但 <a href="https://github.com/vuejs/vue/blob/dev/dist/README.md">README
文档</a>对它们做了比较详尽的说明，规则是名字中带
runtime 只包含运行时，不包含编译器，不带 runtime
的同时包含运行时和编译器，名字中带 common 的是 CommonJS 模块，带 esm
的是 ES 模块，两者都不带的是 UMD 模块。</p>
<p>在 Vue 的 package.json 中，定义了一系列模块接口：</p>
<div class="highlight"><pre><span></span><code><span class="err">  &quot;main&quot;: &quot;dist/vue.runtime.common.js&quot;,</span>
<span class="err">  &quot;module&quot;: &quot;dist/vue.runtime.esm.js&quot;,</span>
<span class="err">  &quot;unpkg&quot;: &quot;dist/vue.js&quot;,</span>
<span class="err">  &quot;jsdelivr&quot;: &quot;dist/vue.js&quot;,</span>
<span class="err">  &quot;typings&quot;: &quot;types/index.d.ts&quot;,</span>
</code></pre></div>


<p>其中 main 是 CommonJS 模块 require() 导入的接口，module 是 ES 模块
import 导入的接口。</p>
<p>让IDE识别动态的Webpack别名（alias）</p>
<p>我没有IDE是否可以识别Webpack配置文件中定义的别名的经验，在使用其它工具调用Webpack的方式打包项目时，Webpack配置文件是动态生成的，这时需要在jsconfig.json的compilerOptions.paths中定义相应的内容。</p>
<p>可以定义顶级模块的别名：</p>
<div class="highlight"><pre><span></span><code>      <span class="s2">&quot;vue-navigation&quot;</span><span class="err">:</span> <span class="p">[</span>
        <span class="s2">&quot;external/vue-navigation&quot;</span>
      <span class="p">]</span>
</code></pre></div>


<p>也可以定义子模块的别名：</p>
<div class="highlight"><pre><span></span><code>      <span class="s2">&quot;components/*&quot;</span><span class="err">:</span> <span class="p">[</span>
        <span class="s2">&quot;src/components/*&quot;</span>
      <span class="p">]</span><span class="err">,</span>
</code></pre></div>


<script type="text/javascript">if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width < 768) ? "left" : align;
        indent = (screen.width < 768) ? "0em" : indent;
        linebreak = (screen.width < 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
</script>
    </div>
<nav class="nav sidebar">
    <div class="siteinfo">
        <div class="sitename"><a href="../..">coinfaces</a></div>
        <div class="tagline">Another Side of Life</div>
    </div>

            <a href="../..">&larr; Home</a>

</nav><footer class="footer">
    <span>
        &copy; jpli &middot;
        under <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA 4.0</a>
    </span>
    |
    <span>
        Powered by:
        <a href="http://blog.getpelican.com/">Pelican</a>
    </span>
</footer></body>
</html>